]> git.mxchange.org Git - flightgear.git/blob - src/Environment/fgclouds.cxx
b61a5510147dcb78993ce8cbb42861d4738dbd4b
[flightgear.git] / src / Environment / fgclouds.cxx
1 // Build a cloud layer based on metar
2 //
3 // Written by Harald JOHNSEN, started April 2005.
4 //
5 // Copyright (C) 2005  Harald JOHNSEN - hjohnsen@evc.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, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
20 //
21 //
22
23 #include <Main/fg_props.hxx>
24
25 #include <simgear/constants.h>
26 #include <simgear/sound/soundmgr_openal.hxx>
27 #include <simgear/scene/sky/sky.hxx>
28 #include <simgear/environment/visual_enviro.hxx>
29 #include <simgear/scene/sky/cloudfield.hxx>
30 #include <simgear/scene/sky/newcloud.hxx>
31 #include <simgear/math/sg_random.h>
32 #include <Main/globals.hxx>
33 #include <Airports/simple.hxx>
34 #include <Main/util.hxx>
35
36 #include "environment_ctrl.hxx"
37 #include "environment_mgr.hxx"
38 #include "fgmetar.hxx"
39 #include "fgclouds.hxx"
40
41 extern SGSky *thesky;
42
43
44 FGClouds::FGClouds(FGEnvironmentCtrl * controller) :
45         station_elevation_ft(0.0),
46         _controller( controller ),
47         snd_lightning(NULL)
48 {
49         update_event = 0;
50         fgSetString("/environment/weather-scenario", "METAR");
51 }
52 FGClouds::~FGClouds() {
53 }
54
55 int FGClouds::get_update_event(void) const {
56         return update_event;
57 }
58 void FGClouds::set_update_event(int count) {
59         update_event = count;
60         build();
61 }
62
63 void FGClouds::init(void) {
64         if( snd_lightning == NULL ) {
65                 snd_lightning = new SGSoundSample(globals->get_fg_root().c_str(), "Sounds/thunder.wav", true);
66                 snd_lightning->set_max_dist(7000.0f);
67                 snd_lightning->set_reference_dist(3000.0f);
68                 SGSoundMgr *soundMgr = globals->get_soundmgr();
69                 soundMgr->add( snd_lightning, "thunder" );
70                 sgEnviro.set_soundMgr( soundMgr );
71         }
72 }
73
74 SGNewCloud *FGClouds::buildCloud(SGPropertyNode *cloud_def_root, string name) {
75         SGPropertyNode *cld_def=NULL;
76
77         cld_def = cloud_def_root->getChild(name.c_str());
78         string base_name = name.substr(0,2);
79         if( !cld_def ) {
80                 if( name[2] == '-' ) {
81                         cld_def = cloud_def_root->getChild(base_name.c_str());
82                 }
83                 if( !cld_def )
84                         return NULL;
85         }
86         string familly = cld_def->getStringValue("familly", base_name.c_str());
87         SGNewCloud *cld = new SGNewCloud(familly);
88         for(int i = 0; i < cld_def->nChildren() ; i++) {
89                 SGPropertyNode *abox = cld_def->getChild(i);
90                 if( strcmp(abox->getName(), "box") == 0) {
91                         double x = abox->getDoubleValue("x");
92                         double y = abox->getDoubleValue("y");
93                         double z = abox->getDoubleValue("z");
94                         double size = abox->getDoubleValue("size");
95                         int type = abox->getIntValue("type", SGNewCloud::CLbox_standard);
96                         cld->addContainer(x, y, z, size, (SGNewCloud::CLbox_type) type);
97                 }
98         }
99         cld->genSprites();
100         return cld;
101 }
102
103 void FGClouds::buildLayer(SGCloudField *layer, string name, double alt, double coverage) {
104         struct {
105                 string name;
106                 double count;
107         } tCloudVariety[20];
108         int CloudVarietyCount = 0;
109         double totalCount = 0.0;
110
111         SGPropertyNode *cloud_def_root = fgGetNode("/environment/config/cloudlayers/clouds", false);
112         SGPropertyNode *layer_def_root = fgGetNode("/environment/config/cloudlayers/layers", false);
113
114         layer->clear();
115         // when we don't generate clouds the layer is rendered in 2D
116         if( coverage == 0.0 )
117                 return;
118         if( layer_def_root == NULL || cloud_def_root == NULL)
119                 return;
120         if( name == "ci" || name == "sc" || name == "st")
121                 return;
122
123         SGPropertyNode *layer_def=NULL;
124
125         layer_def = layer_def_root->getChild(name.c_str());
126         if( !layer_def ) {
127                 if( name[2] == '-' ) {
128                         string base_name = name.substr(0,2);
129                         layer_def = layer_def_root->getChild(base_name.c_str());
130                 }
131                 if( !layer_def )
132                         return;
133         }
134
135         double grid_x_size = layer_def->getDoubleValue("grid-x-size", 1000.0);
136         double grid_y_size = layer_def->getDoubleValue("grid-y-size", 1000.0);
137         double grid_x_rand = layer_def->getDoubleValue("grid-x-rand", grid_x_size);
138         double grid_y_rand = layer_def->getDoubleValue("grid-y-rand", grid_y_size);
139         double grid_z_rand = layer_def->getDoubleValue("grid-z-rand");
140
141         for(int i = 0; i < layer_def->nChildren() ; i++) {
142                 SGPropertyNode *acloud = layer_def->getChild(i);
143                 if( strcmp(acloud->getName(), "cloud") == 0) {
144                         string cloud_name = acloud->getStringValue("name");
145                         tCloudVariety[CloudVarietyCount].name = cloud_name;
146                         double count = acloud->getDoubleValue("count", 1.0);
147                         tCloudVariety[CloudVarietyCount].count = count;
148                         int variety = 0;
149                         cloud_name = cloud_name + "-%d";
150                         char variety_name[50];
151                         do {
152                                 variety++;
153                                 snprintf(variety_name, sizeof(variety_name), cloud_name.c_str(), variety);
154                         } while( cloud_def_root->getChild(variety_name, 0, false) );
155
156                         totalCount += count;
157                         if( CloudVarietyCount < 20 )
158                                 CloudVarietyCount++;
159                 }
160         }
161         totalCount = 1.0 / totalCount;
162         double currCoverage = 0.0;
163
164         for(double px = 0.0; px < SGCloudField::fieldSize; px += grid_x_size) {
165                 for(double py = 0.0; py < SGCloudField::fieldSize; py += grid_y_size) {
166                         double x = px + grid_x_rand * (sg_random() - 0.5);
167                         double y = py + grid_y_rand * (sg_random() - 0.5);
168                         double z = alt + grid_z_rand * (sg_random() - 0.5);
169                         double choice = sg_random();
170                         currCoverage += coverage;
171                         if( currCoverage < 1.0 )
172                                 continue;
173                         currCoverage -= 1.0;
174
175                         for(int i = 0; i < CloudVarietyCount ; i ++) {
176                                 choice -= tCloudVariety[i].count * totalCount;
177                                 if( choice <= 0.0 ) {
178                                         SGNewCloud *cld = buildCloud(cloud_def_root, tCloudVariety[i].name);
179                                         sgVec3 pos={x,z,y};
180                                         if( cld )
181                                                 layer->addCloud(pos, cld);
182
183                                         break;
184                                 }
185                         }
186                 }
187         }
188
189 }
190
191 // TODO:call this after real metar updates
192 void FGClouds::buildMETAR(void) {
193         SGPropertyNode *metar_root = fgGetNode("/environment", true);
194
195         double wind_speed_kt     = metar_root->getDoubleValue("wind-speed-kt");
196         double temperature_degc  = metar_root->getDoubleValue("temperature-sea-level-degc");
197         double dewpoint_degc     = metar_root->getDoubleValue("dewpoint-sea-level-degc");
198         double pressure_mb              = metar_root->getDoubleValue("pressure-sea-level-inhg") * SG_INHG_TO_PA / 100.0;
199
200         double dewp = pow(10.0, 7.5 * dewpoint_degc / (237.7 + dewpoint_degc));
201         double temp = pow(10.0, 7.5 * temperature_degc / (237.7 + temperature_degc));
202         double rel_humidity = dewp * 100 / temp;
203
204         // formule d'Epsy, base d'un cumulus
205         double cumulus_base = 122.0 * (temperature_degc - dewpoint_degc);
206         double stratus_base = 100.0 * (100.0 - rel_humidity) * SG_FEET_TO_METER;
207
208         bool cu_seen = false;
209
210         for(int iLayer = 0 ; iLayer < thesky->get_cloud_layer_count(); iLayer++) {
211                 SGPropertyNode *cloud_root = fgGetNode("/environment/clouds/layer", iLayer, true);
212
213                 double alt_ft = cloud_root->getDoubleValue("elevation-ft");
214                 double alt_m = alt_ft * SG_FEET_TO_METER;
215                 string coverage = cloud_root->getStringValue("coverage");
216                 double coverage_norm = 0.0;
217                 if( coverage == "few" )
218                         coverage_norm = 2.0/8.0;        // <1-2
219                 else if( coverage == "scattered" )
220                         coverage_norm = 4.0/8.0;        // 3-4
221                 else if( coverage == "broken" )
222                         coverage_norm = 6.0/8.0;        // 5-7
223                 else if( coverage == "overcast" )
224                         coverage_norm = 8.0/8.0;        // 8
225
226                 string layer_type = "nn";
227                 if( coverage == "cirrus" ) {
228                         layer_type = "ci";
229                 } else if( alt_ft > 16500 ) {
230 //                      layer_type = "ci|cs|cc";
231                         layer_type = "ci";
232                 } else if( alt_ft > 6500 ) {
233 //                      layer_type = "as|ac|ns";
234                         layer_type = "ac";
235                         if( pressure_mb < 1005.0 && coverage_norm >= 5.5 )
236                                 layer_type = "ns";
237                 } else {
238 //                      layer_type = "st|cu|cb|sc";
239                         // +/- 20% from stratus probable base
240                         if( stratus_base * 0.80 < alt_m && stratus_base * 1.40 > alt_m )
241                                 layer_type = "st";
242                         // +/- 20% from cumulus probable base
243                         else if( cumulus_base * 0.80 < alt_m && cumulus_base * 1.20 > alt_m )
244                                 layer_type = "cu";
245                         else {
246                                 // above formulae is far from perfect
247                                 if ( alt_ft < 2000 )
248                                         layer_type = "st";
249                                 else if( alt_ft < 4500 )
250                                         layer_type = "cu";
251                                 else
252                                         layer_type = "sc";
253                         }
254                 }
255
256                 SGCloudField *layer3D = thesky->get_cloud_layer(iLayer)->get_layer3D();
257                 buildLayer(layer3D, layer_type, alt_m, coverage_norm);
258         }
259 }
260
261 // copy from FGMetarEnvironmentCtrl until better
262 void
263 FGClouds::update_metar_properties( FGMetar *m )
264 {
265     int i;
266     double d;
267     char s[128];
268
269     fgSetString("/environment/metar/station-id", m->getId());
270     fgSetDouble("/environment/metar/min-visibility-m",
271                 m->getMinVisibility().getVisibility_m() );
272     fgSetDouble("/environment/metar/max-visibility-m",
273                 m->getMaxVisibility().getVisibility_m() );
274
275     SGMetarVisibility *dirvis = m->getDirVisibility();
276     for (i = 0; i < 8; i++, dirvis++) {
277         const char *min = "/environment/metar/visibility[%d]/min-m";
278         const char *max = "/environment/metar/visibility[%d]/max-m";
279
280         d = dirvis->getVisibility_m();
281
282         snprintf(s, 128, min, i);
283         fgSetDouble(s, d);
284         snprintf(s, 128, max, i);
285         fgSetDouble(s, d);
286     }
287
288     fgSetInt("/environment/metar/base-wind-range-from",
289              m->getWindRangeFrom() );
290     fgSetInt("/environment/metar/base-wind-range-to",
291              m->getWindRangeTo() );
292     fgSetDouble("/environment/metar/base-wind-speed-kt",
293                 m->getWindSpeed_kt() );
294     fgSetDouble("/environment/metar/gust-wind-speed-kt",
295                 m->getGustSpeed_kt() );
296     fgSetDouble("/environment/metar/temperature-degc",
297                 m->getTemperature_C() );
298     fgSetDouble("/environment/metar/dewpoint-degc",
299                 m->getDewpoint_C() );
300     fgSetDouble("/environment/metar/rel-humidity-norm",
301                 m->getRelHumidity() );
302     fgSetDouble("/environment/metar/pressure-inhg",
303                 m->getPressure_inHg() );
304
305     vector<SGMetarCloud> cv = m->getClouds();
306     vector<SGMetarCloud>::iterator cloud;
307
308     const char *cl = "/environment/clouds/layer[%i]";
309     for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
310         const char *coverage_string[5] = 
311             { "clear", "few", "scattered", "broken", "overcast" };
312         const double thickness[5] = { 0, 65, 600,750, 1000};
313         int q;
314
315         snprintf(s, 128, cl, i);
316         strncat(s, "/coverage", 128);
317         q = cloud->getCoverage();
318         fgSetString(s, coverage_string[q] );
319
320         snprintf(s, 128, cl, i);
321         strncat(s, "/elevation-ft", 128);
322         fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
323
324         snprintf(s, 128, cl, i);
325         strncat(s, "/thickness-ft", 128);
326         fgSetDouble(s, thickness[q]);
327
328         snprintf(s, 128, cl, i);
329         strncat(s, "/span-m", 128);
330         fgSetDouble(s, 40000.0);
331     }
332
333     for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
334         snprintf(s, 128, cl, i);
335         strncat(s, "/coverage", 128);
336         fgSetString(s, "clear");
337
338         snprintf(s, 128, cl, i);
339         strncat(s, "/elevation-ft", 128);
340         fgSetDouble(s, -9999);
341
342         snprintf(s, 128, cl, i);
343         strncat(s, "/thickness-ft", 128);
344         fgSetDouble(s, 0);
345
346         snprintf(s, 128, cl, i);
347         strncat(s, "/span-m", 128);
348         fgSetDouble(s, 40000.0);
349     }
350
351     fgSetDouble("/environment/metar/rain-norm", m->getRain());
352     fgSetDouble("/environment/metar/hail-norm", m->getHail());
353     fgSetDouble("/environment/metar/snow-norm", m->getSnow());
354     fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
355 }
356
357 void
358 FGClouds::update_env_config ()
359 {
360     fgSetupWind( fgGetDouble("/environment/metar/base-wind-range-from"),
361                  fgGetDouble("/environment/metar/base-wind-range-to"),
362                  fgGetDouble("/environment/metar/base-wind-speed-kt"),
363                  fgGetDouble("/environment/metar/gust-wind-speed-kt") );
364
365     fgDefaultWeatherValue( "visibility-m",
366                            fgGetDouble("/environment/metar/min-visibility-m") );
367 #if 0
368     set_temp_at_altitude( fgGetDouble("/environment/metar/temperature-degc"),
369                           station_elevation_ft );
370     set_dewpoint_at_altitude( fgGetDouble("/environment/metar/dewpoint-degc"),
371                               station_elevation_ft );
372 #endif
373     fgDefaultWeatherValue( "pressure-sea-level-inhg",
374                            fgGetDouble("/environment/metar/pressure-inhg") );
375 }
376
377
378 void FGClouds::setLayer( int iLayer, float alt_ft, string coverage, string layer_type ) {
379         double coverage_norm = 0.0;
380         if( coverage == "few" )
381                 coverage_norm = 2.0/8.0;        // <1-2
382         else if( coverage == "scattered" )
383                 coverage_norm = 4.0/8.0;        // 3-4
384         else if( coverage == "broken" )
385                 coverage_norm = 6.0/8.0;        // 5-7
386         else if( coverage == "overcast" )
387                 coverage_norm = 8.0/8.0;        // 8
388
389         SGCloudField *layer3D = thesky->get_cloud_layer(iLayer)->get_layer3D();
390         buildLayer(layer3D, layer_type, station_elevation_ft + alt_ft * SG_FEET_TO_METER, coverage_norm);
391 }
392
393 void FGClouds::buildScenario( string scenario ) {
394         string fakeMetar="";
395         string station = fgGetString("/environment/metar/station-id", "XXXX");
396
397         // fetch station elevation if exists
398     FGAirport a = globals->get_airports()->search( station );
399     station_elevation_ft = a.getElevation();
400
401         for(int iLayer = 0 ; iLayer < thesky->get_cloud_layer_count(); iLayer++) {
402                 thesky->get_cloud_layer(iLayer)->get_layer3D()->clear();
403         }
404
405         station += " 011000Z ";
406         if( scenario == "Fair weather" ) {
407                 fakeMetar = "15003KT 12SM SCT033 FEW200 20/08 Q1015 NOSIG";
408                 setLayer(0, 3300.0, "scattered", "cu");
409         } else if( scenario == "Thunderstorm" ) {
410                 fakeMetar = "15012KT 08SM TSRA SCT040 BKN070 20/12 Q0995";
411                 setLayer(0, 4000.0, "scattered", "cb");
412                 setLayer(1, 7000.0, "scattered", "ns");
413         } else 
414                 return;
415         FGMetar *m = new FGMetar( station + fakeMetar );
416         update_metar_properties( m );
417         update_env_config();
418         // propagate aloft tables
419         _controller->reinit();
420
421         fgSetString("/environment/metar/last-metar", m->getData());
422         // TODO:desactivate real metar updates
423         if( scenario == "Fair weather" ) {
424                 fgSetString("/environment/clouds/layer[1]/coverage", "cirrus");
425         }
426 }
427
428
429 void FGClouds::build(void) {
430         string scenario = fgGetString("/environment/weather-scenario", "METAR");
431
432         if( scenario == "METAR" ) {
433                 string realMetar = fgGetString("/environment/metar/real-metar", "");
434                 if( realMetar != "" ) {
435                         fgSetString("/environment/metar/last-metar", realMetar.c_str());
436                         FGMetar *m = new FGMetar( realMetar );
437                         update_metar_properties( m );
438                         update_env_config();
439                         // propagate aloft tables
440                         _controller->reinit();
441                 }
442                 buildMETAR();
443         }
444         else
445                 buildScenario( scenario );
446
447         // ...
448         if( snd_lightning == NULL )
449                 init();
450 }