FlightGear next
AIThermal.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: AIThermal.cxx
3 * SPDX-FileComment: AIBase-derived class creates an AI thermal. An attempt to refine the thermal shape and behaviour by WooT 2009
4 * SPDX-FileCopyrightText: Copyright (C) 2004  David P. Culp - davidculp2@comcast.net
5 * SPDX-FileContributor: Copyright (C) 2009 Patrice Poly ( WooT )
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include <cmath>
10#include <string>
11
12#include <Main/fg_props.hxx>
13#include <Main/globals.hxx>
14#include <Scenery/scenery.hxx>
15
16#include "AIThermal.hxx"
17
18
20{
21 altitude_agl_ft = 0.0;
22 max_strength = 6.0;
23 diameter = 0.5;
24 strength = factor = 0.0;
25 cycle_timer = 60 * (rand() % 31); // some random in the birth time
26 ground_elev_ft = 0.0;
27 dt_count = 0.9;
28 alt = 0.0;
29}
30
31
32void FGAIThermal::readFromScenario(SGPropertyNode* scFileNode)
33{
34 if (!scFileNode)
35 return;
36
38
39 setMaxStrength(scFileNode->getDoubleValue("strength-fps", 8.0));
40 setDiameter(scFileNode->getDoubleValue("diameter-ft", 0.0) / 6076.11549);
41 setHeight(scFileNode->getDoubleValue("height-msl", 5000.0));
42}
43
45{
46 factor = 8.0 * max_strength / (diameter * diameter * diameter);
47 setAltitude(height);
48 _surface_wind_from_deg_node =
49 fgGetNode("/environment/config/boundary/entry[0]/wind-from-heading-deg", true);
50 _surface_wind_speed_node =
51 fgGetNode("/environment/config/boundary/entry[0]/wind-speed-kt", true);
52 _aloft_wind_from_deg_node =
53 fgGetNode("/environment/config/aloft/entry[2]/wind-from-heading-deg", true);
54 _aloft_wind_speed_node =
55 fgGetNode("/environment/config/aloft/entry[2]/wind-speed-kt", true);
56 do_agl_calc = true;
57 return FGAIBase::init(searchOrder);
58}
59
61{
63 tie("position/altitude-agl-ft", SGRawValuePointer<double>(&altitude_agl_ft));
64 tie("alt-rel", SGRawValuePointer<double>(&alt_rel));
65 tie("time", SGRawValuePointer<double>(&time));
66 tie("xx", SGRawValuePointer<double>(&xx));
67 tie("is-forming", SGRawValuePointer<bool>(&is_forming));
68 tie("is-formed", SGRawValuePointer<bool>(&is_formed));
69 tie("is-dying", SGRawValuePointer<bool>(&is_dying));
70 tie("is-dead", SGRawValuePointer<bool>(&is_dead));
71}
72
73void FGAIThermal::update(double dt)
74{
76 Run(dt);
77 Transform();
78}
79
80
81//the formula to get the available portion of VUpMax depending on altitude
82//returns a double between 0 and 1
83double FGAIThermal::get_strength_fac(double alt_frac)
84{
85 double PI = 4.0 * atan(1.0);
86 double fac = 0.0;
87
88 if (alt_frac <= 0.0) { // do submarines get thermals ?
89 fac = 0.0;
90 } else if ((alt_frac > 0.0) && (alt_frac <= 0.1)) { // ground layer
91 fac = (0.1 * (pow((10.0 * alt_frac), 10.0)));
92 } else if ((alt_frac > 0.1) && (alt_frac <= 1.0)) { // main body of the thermal
93 fac = 0.4175 - 0.5825 * (cos(PI * (1.0 - sqrt(alt_frac)) + PI));
94 } else if ((alt_frac > 1.0) && (alt_frac < 1.1)) { //above the ceiling, but not above the cloud
95 fac = (0.5 * (1.0 + cos(PI * ((-2.0 * alt_frac) * 5.0))));
96 } else if (alt_frac >= 1.1) { //above the cloud
97 fac = 0.0;
98 }
99
100 return fac;
101}
102
103
104void FGAIThermal::Run(double dt)
105{
106 // *****************************************
107 // the thermal characteristics and variables
108 // *****************************************
109
110 cycle_timer += dt;
111
112 // time
113
114 // the time needed for the thermal to be completely formed
115 const double tmin1 = 5.0;
116 // the time when the thermal begins to die
117 const double tmin2 = 20.0;
118 // the time when the thermal is completely dead
119 const double tmin3 = 25.0;
120 const double alive_cycle_time = tmin3 * 60.0;
121 //the time of the complete cycle, including a period of inactivity
122 const double tmin4 = 30.0;
123 // some times expressed in a fraction of tmin3;
124 const double t1 = tmin1 / tmin3;
125 const double t2 = tmin2 / tmin3;
126 const double t3 = 1.0;
127 const double t4 = tmin4 / tmin3;
128 // the time elapsed since the thermal was born, in a 0-1 fraction of tmin3
129
130 time = cycle_timer / alive_cycle_time;
131 // comment above and uncomment below to freeze the time cycle
132 // time=0.5;
133
134 if (time >= t4) {
135 cycle_timer = 60 * (rand() % 31);
136 }
137
138 //the position of the thermal 'top'
139 double thermal_foot_lat = (pos.getLatitudeDeg());
140 double thermal_foot_lon = (pos.getLongitudeDeg());
141
142 //the max updraft one can expect in this thermal
143 double MaxUpdraft = max_strength;
144 //the max sink one can expect in this thermal, this is a negative number
145 double MinUpdraft = -max_strength * 0.25;
146 //the fraction of MaxUpdraft one can expect at our height and time
147 double maxstrengthavail;
148 double wind_speed;
149
150
151 //max radius of the the thermal, including the sink area
152 const double Rmax = diameter / 2.0;
153 // 'shaping' of the thermal, the higher, the more conical the thermal- between 0 and 1
154 const double shaping = 0.8;
155 //the radius of the thermal at our altitude in FT, including sink
156 double Rsink;
157 //radius of the thermal where we have updraft, in FT
158 double Rup;
159 //how far are we from the thermal center at our altitude in FEET
160 double dist_center;
161
162 //the position of the center of the thermal slice at our altitude
163 double slice_center_lon;
164 double slice_center_lat;
165
166 //we need to know the thermal foot AGL altitude
167
168 //we could do this only once, as thermal don't move
169 //but then agl info is lost on user reset
170 //so we only do this every 10 seconds to save cpu
171 dt_count += dt;
172 if (dt_count >= 10.0) {
173 if (getGroundElevationM(SGGeod::fromGeodM(pos, 20000), alt, 0)) {
174 ground_elev_ft = alt * SG_METER_TO_FEET;
175 do_agl_calc = false;
176 altitude_agl_ft = height - ground_elev_ft;
177 dt_count = 0.0;
178 }
179 }
180
181 //user altitude relative to the thermal height, seen AGL from the thermal foot
182
183 double user_altitude = globals->get_aircraft_position().getElevationFt();
184 if (user_altitude < 1.0) { user_altitude = 1.0; }; // an ugly way to avoid NaNs for users at alt 0
185 double user_altitude_agl = (user_altitude - ground_elev_ft);
186 alt_rel = user_altitude_agl / altitude_agl_ft;
187
188
189 //the updraft user feels !
190 double Vup;
191
192 // *********************
193 // environment variables
194 // *********************
195
196 // the wind heading at the user alt
197 double wind_heading_rad;
198
199 // the "ambient" sink outside thermals
200 double global_sink = -1.0;
201
202 // **************
203 // some constants
204 // **************
205
206 double PI = 4.0 * atan(1.0);
207
208
209 // ******************
210 // thermal main cycle
211 // ******************
212
213 //we get the max strength proportion we can expect at the time and altitude, clamped between 0 and 1
214 //double xx;
215 if (time <= t1) {
216 xx = (time / t1);
217 maxstrengthavail = xx * get_strength_fac(alt_rel / xx);
218
219 is_forming = true;
220 is_formed = false;
221 is_dying = false;
222 is_dead = false;
223
224 } else if ((time > t1) && (time <= t2)) {
225 maxstrengthavail = get_strength_fac((alt_rel));
226
227 is_forming = false;
228 is_formed = true;
229 is_dying = false;
230 is_dead = false;
231
232 } else if ((time > t2) && (time <= t3)) {
233 xx = ((time - t2) / (1.0 - t2));
234 maxstrengthavail = get_strength_fac(alt_rel - xx);
235
236 is_forming = false;
237 is_formed = false;
238 is_dying = true;
239 is_dead = false;
240
241 } else {
242 maxstrengthavail = 0.0;
243 is_forming = false;
244 is_formed = false;
245 is_dying = false;
246 is_dead = true;
247 }
248
249 //we get the diameter of the thermal slice at the user altitude
250 //the thermal has a slight conic shape
251
252 if ((alt_rel >= 0.0) && (alt_rel < 1.0)) {
253 Rsink = (shaping * Rmax) + (((1.0 - shaping) * Rmax * alt_rel) / altitude_agl_ft); // in the main thermal body
254 } else if ((alt_rel >= 1.0) && (alt_rel < 1.1)) {
255 Rsink = (Rmax / 2.0) * (1.0 + cos((10.0 * PI * alt_rel) - (2.0 * PI))); // above the ceiling
256 } else {
257 Rsink = 0.0; // above the cloud
258 }
259
260 //we get the portion of the diameter that produces lift
261 Rup = r_up_frac * Rsink;
262
263 //we now determine v_up_max and VupMin depending on our altitude
264
265 v_up_max = maxstrengthavail * MaxUpdraft;
266 v_up_min = maxstrengthavail * MinUpdraft;
267
268 // Now we know, for current t and alt, v_up_max, VupMin, Rup, Rsink.
269
270 // We still need to know how far we are from the thermal center
271
272 // To determine the thermal inclination in the wind, we use a ugly approximation,
273 // in which we say the thermal bends 20° (0.34906 rad ) for 10 kts wind.
274 // We move the thermal foot upwind, to keep the cloud model over the "center" at ceiling level.
275 // the displacement distance of the center of the thermal slice, at user altitude,
276 // and relative to a hypothetical vertical thermal, would be:
277
278 // get surface and 9000 ft wind
279
280 double ground_wind_from_deg = _surface_wind_from_deg_node->getDoubleValue();
281 double ground_wind_speed_kts = _surface_wind_speed_node->getDoubleValue();
282 double aloft_wind_from_deg = _aloft_wind_from_deg_node->getDoubleValue();
283 double aloft_wind_speed_kts = _aloft_wind_speed_node->getDoubleValue();
284
285 double ground_wind_from_rad = (PI / 2.0) - PI * (ground_wind_from_deg / 180.0);
286 double aloft_wind_from_rad = (PI / 2.0) - PI * (aloft_wind_from_deg / 180.0);
287
288 wind_heading_rad = PI + 0.5 * (ground_wind_from_rad + aloft_wind_from_rad);
289
290 wind_speed = ground_wind_speed_kts + user_altitude * ((aloft_wind_speed_kts - ground_wind_speed_kts) / 9000.0);
291
292 double dt_center_alt = -(tan(0.034906 * wind_speed)) * (altitude_agl_ft - user_altitude_agl);
293
294 // now, lets find how far we are from this shifted slice
295
296 double dt_slice_lon_FT = (dt_center_alt * cos(wind_heading_rad));
297 double dt_slice_lat_FT = (dt_center_alt * sin(wind_heading_rad));
298
299 double dt_slice_lon = dt_slice_lon_FT / ft_per_deg_lon;
300 double dt_slice_lat = dt_slice_lat_FT / ft_per_deg_lat;
301
302 slice_center_lon = thermal_foot_lon + dt_slice_lon;
303 slice_center_lat = thermal_foot_lat + dt_slice_lat;
304
305 dist_center = SGGeodesy::distanceNm(SGGeod::fromDeg(slice_center_lon, slice_center_lat),
307
308 // Now we can calculate Vup
309
310 if (max_strength >= 0.0) { // this is a thermal
311
312 if ((dist_center >= 0.0) && (dist_center < Rup)) { //user is in the updraft area
313 Vup = v_up_max * cos(dist_center * PI / (2.0 * Rup));
314 } else if ((dist_center > Rup) && (dist_center <= ((Rup + Rsink) / 2.0))) { //user in the 1st half of the sink area
315 Vup = v_up_min * cos((dist_center - (Rup + Rsink) / 2.0) * PI / (2.0 * ((Rup + Rsink) / 2.0 - Rup)));
316 } else if ((dist_center > ((Rup + Rsink) / 2.0)) && dist_center <= Rsink) { // user in the 2nd half of the sink area
317 Vup = (global_sink + v_up_min) / 2.0 + (global_sink - v_up_min) / 2.0 * cos((dist_center - Rsink) * PI / ((Rsink - Rup) / 2.0));
318 } else { // outside the thermal
319 Vup = global_sink;
320 }
321 }
322
323 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
324 if (alt_rel <= 1.1) {
325 double fac = (1.0 - (1.0 - 1.815 * alt_rel) * (1.0 - 1.815 * alt_rel));
326 Vup = fac * (global_sink + ((v_up_max - global_sink) / 2.0) * (1.0 + cos(dist_center * PI / Rmax)));
327 } else {
328 Vup = global_sink;
329 }
330 }
331
332 //correct for no global sink above clouds and outside thermals
333 if (((alt_rel > 1.0) && (alt_rel < 1.1)) && (dist_center > Rsink)) {
334 Vup = global_sink * (11.0 - 10.0 * alt_rel);
335 }
336
337 if (alt_rel >= 1.1) {
338 Vup = 0.0;
339 }
340
341 strength = Vup;
342 range = dist_center;
343}
SGGeod pos
Definition AIBase.hxx:212
virtual void readFromScenario(SGPropertyNode *scFileNode)
Definition AIBase.cxx:249
FGAIBase(object_type ot, bool enableHot)
Definition AIBase.cxx:146
bool getGroundElevationM(const SGGeod &pos, double &elev, const simgear::BVHMaterial **material) const
Definition AIBase.cxx:904
virtual bool init(ModelSearchOrder searchOrder)
Definition AIBase.cxx:633
void setAltitude(double altitude_ft)
Definition AIBase.hxx:419
virtual void update(double dt)
Definition AIBase.cxx:294
double altitude_agl_ft
Definition AIBase.hxx:223
double range
Definition AIBase.hxx:241
void Transform()
Definition AIBase.cxx:518
double ft_per_deg_lon
Definition AIBase.hxx:225
ModelSearchOrder
Definition AIBase.hxx:63
double ft_per_deg_lat
Definition AIBase.hxx:226
void tie(const char *aRelPath, const SGRawValue< T > &aRawValue)
Tied-properties helper, record nodes which are tied for easy un-tie-ing.
Definition AIBase.hxx:198
virtual void bind()
Definition AIBase.cxx:713
void setHeight(double h)
Definition AIThermal.hxx:33
void setMaxStrength(double s)
Definition AIThermal.hxx:31
void readFromScenario(SGPropertyNode *scFileNode) override
Definition AIThermal.cxx:32
void bind() override
Definition AIThermal.cxx:60
void setDiameter(double d)
Definition AIThermal.hxx:32
void update(double dt) override
Definition AIThermal.cxx:73
bool init(ModelSearchOrder searchOrder) override
Definition AIThermal.cxx:44
SGGeod get_aircraft_position() const
Definition globals.cxx:611
FGGlobals * globals
Definition globals.cxx:142
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27