]> git.mxchange.org Git - flightgear.git/blob - src/Environment/fgclouds.cxx
0c6d45624a26991b174018c229e182f53d3d577f
[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, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 //
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #endif
26
27 #include <Main/fg_props.hxx>
28
29 #include <simgear/constants.h>
30 #include <simgear/sound/soundmgr_openal.hxx>
31 #include <simgear/scene/sky/sky.hxx>
32 #include <simgear/environment/visual_enviro.hxx>
33 #include <simgear/scene/sky/cloudfield.hxx>
34 #include <simgear/scene/sky/newcloud.hxx>
35 #include <simgear/math/sg_random.h>
36 #include <simgear/props/props_io.hxx>
37
38 #include <Main/globals.hxx>
39 #include <Airports/simple.hxx>
40 #include <Main/util.hxx>
41
42 #include "environment_ctrl.hxx"
43 #include "environment_mgr.hxx"
44 #include "fgmetar.hxx"
45 #include "fgclouds.hxx"
46
47 extern SGSky *thesky;
48
49
50 FGClouds::FGClouds(FGEnvironmentCtrl * controller) :
51         station_elevation_ft(0.0),
52         _controller( controller ),
53         snd_lightning(NULL),
54     last_scenario( "unset" ),
55     last_env_config( new SGPropertyNode() ),
56     last_env_clouds( new SGPropertyNode() )
57 {
58         update_event = 0;
59 }
60 FGClouds::~FGClouds() {
61 }
62
63 int FGClouds::get_update_event(void) const {
64         return update_event;
65 }
66 void FGClouds::set_update_event(int count) {
67         update_event = count;
68         build();
69 }
70
71 void FGClouds::init(void) {
72         if( snd_lightning == NULL ) {
73                 snd_lightning = new SGSoundSample(globals->get_fg_root().c_str(), "Sounds/thunder.wav");
74                 snd_lightning->set_max_dist(7000.0f);
75                 snd_lightning->set_reference_dist(3000.0f);
76                 SGSoundMgr *soundMgr = globals->get_soundmgr();
77                 soundMgr->add( snd_lightning, "thunder" );
78                 sgEnviro.set_soundMgr( soundMgr );
79         }
80 }
81
82 void FGClouds::buildCloud(SGPropertyNode *cloud_def_root, SGPropertyNode  *box_def_root, const string& name, sgVec3 pos, SGCloudField *layer) {
83         SGPropertyNode *box_def=NULL;
84         SGPropertyNode *cld_def=NULL;
85         
86         SGPath texture_root = globals->get_fg_root();
87         texture_root.append("Textures");
88         texture_root.append("Sky");
89
90         box_def = box_def_root->getChild(name.c_str());
91   
92         string base_name = name.substr(0,2);
93         if( !box_def ) {
94                 if( name[2] == '-' ) {
95                         box_def = box_def_root->getChild(base_name.c_str());
96                 }
97                 if( !box_def )
98                         return;
99         }
100         
101         for(int i = 0; i < box_def->nChildren() ; i++) {
102                 SGPropertyNode *abox = box_def->getChild(i);
103                 if( strcmp(abox->getName(), "box") == 0) {
104                         double x = abox->getDoubleValue("x") + pos[0];
105                         double y = abox->getDoubleValue("y") + pos[1];
106                         double z = abox->getDoubleValue("z") + pos[2];
107                         SGVec3f newpos = SGVec3f(x, z, y);
108                         
109                         string type = abox->getStringValue("type", "cu-small");
110                                
111                         cld_def = cloud_def_root->getChild(type.c_str());
112                         if ( !cld_def ) return;
113                         
114                         double min_width = cld_def->getDoubleValue("min-cloud-width-m", 500.0);
115                         double max_width = cld_def->getDoubleValue("max-cloud-width-m", 1000.0);
116                         double min_height = cld_def->getDoubleValue("min-cloud-height-m", min_width);
117                         double max_height = cld_def->getDoubleValue("max-cloud-height-m", max_width);
118                         double min_sprite_width = cld_def->getDoubleValue("min-sprite-width-m", 200.0);
119                         double max_sprite_width = cld_def->getDoubleValue("max-sprite-width-m", min_sprite_width);
120                         double min_sprite_height = cld_def->getDoubleValue("min-sprite-height-m", min_sprite_width);
121                         double max_sprite_height = cld_def->getDoubleValue("max-sprite-height-m", max_sprite_width);
122                         int num_sprites = cld_def->getIntValue("num-sprites", 20);
123                         int num_textures_x = cld_def->getIntValue("num-textures-x", 1);
124                         int num_textures_y = cld_def->getIntValue("num-textures-y", 1);
125                         double bottom_shade = cld_def->getDoubleValue("bottom-shade", 1.0);
126                         string texture = cld_def->getStringValue("texture", "cl_cumulus.rgb");
127           
128                         SGNewCloud *cld = 
129                                 new SGNewCloud(type,
130                                                texture_root, 
131                                                texture, 
132                                                min_width, 
133                                                max_width, 
134                                                min_height,
135                                                max_height,
136                                                min_sprite_width,
137                                                max_sprite_width,
138                                                min_sprite_height,
139                                                max_sprite_height,
140                                                bottom_shade,
141                                                num_sprites,
142                                                num_textures_x,
143                                                num_textures_y);
144                         layer->addCloud(newpos, cld);
145                 }
146         }
147 }
148
149 void FGClouds::buildLayer(int iLayer, const string& name, double alt, double coverage) {
150         struct {
151                 string name;
152                 double count;
153         } tCloudVariety[20];
154         int CloudVarietyCount = 0;
155         double totalCount = 0.0;
156         
157         SGPropertyNode *cloud_def_root = fgGetNode("/environment/cloudlayers/clouds", false);
158         SGPropertyNode *box_def_root   = fgGetNode("/environment/cloudlayers/boxes", false);
159         SGPropertyNode *layer_def_root = fgGetNode("/environment/cloudlayers/layers", false);
160         SGCloudField *layer = thesky->get_cloud_layer(iLayer)->get_layer3D();
161         layer->clear();
162         
163         // when we don't generate clouds the layer is rendered in 2D
164         if ((! clouds_3d_enabled) || coverage == 0.0 ||
165             layer_def_root == NULL || cloud_def_root == NULL || box_def_root == NULL)
166         {
167                 thesky->get_cloud_layer(iLayer)->set_enable3dClouds(false);
168                 return;
169         }
170         
171         SGPropertyNode *layer_def=NULL;
172
173         layer_def = layer_def_root->getChild(name.c_str());
174         if( !layer_def ) {
175                 if( name[2] == '-' ) {
176                         string base_name = name.substr(0,2);
177                         layer_def = layer_def_root->getChild(base_name.c_str());
178                 }
179                 if( !layer_def )
180                 {
181                         thesky->get_cloud_layer(iLayer)->set_enable3dClouds(false);
182                         return;
183                 }
184         }
185         
186         double grid_x_size = layer_def->getDoubleValue("grid-x-size", 1000.0);
187         double grid_y_size = layer_def->getDoubleValue("grid-y-size", 1000.0);
188         double grid_x_rand = layer_def->getDoubleValue("grid-x-rand", grid_x_size);
189         double grid_y_rand = layer_def->getDoubleValue("grid-y-rand", grid_y_size);
190         double grid_z_rand = layer_def->getDoubleValue("grid-z-rand");
191
192         for(int i = 0; i < layer_def->nChildren() ; i++) {
193                 SGPropertyNode *acloud = layer_def->getChild(i);
194                 if( strcmp(acloud->getName(), "cloud") == 0) {
195                         string cloud_name = acloud->getStringValue("name");
196                         tCloudVariety[CloudVarietyCount].name = cloud_name;
197                         double count = acloud->getDoubleValue("count", 1.0);
198                         tCloudVariety[CloudVarietyCount].count = count;
199                         int variety = 0;
200                         cloud_name = cloud_name + "-%d";
201                         char variety_name[50];
202                         do {
203                                 variety++;
204                                 snprintf(variety_name, sizeof(variety_name), cloud_name.c_str(), variety);
205                         } while( box_def_root->getChild(variety_name, 0, false) );
206
207                         totalCount += count;
208                         if( CloudVarietyCount < 20 )
209                                 CloudVarietyCount++;
210                 }
211         }
212         totalCount = 1.0 / totalCount;
213         
214         for(double px = 0.0; px < SGCloudField::fieldSize; px += grid_x_size) {
215                 for(double py = 0.0; py < SGCloudField::fieldSize; py += grid_y_size) {
216                         double x = px + grid_x_rand * (sg_random() - 0.5) - (SGCloudField::fieldSize / 2.0);
217                         double y = py + grid_y_rand * (sg_random() - 0.5) - (SGCloudField::fieldSize / 2.0);
218                         double z = grid_z_rand * (sg_random() - 0.5);
219                         
220                         if (sg_random() < coverage)
221                         {
222                             double choice = sg_random();
223     
224                             for(int i = 0; i < CloudVarietyCount ; i ++) {
225                                     choice -= tCloudVariety[i].count * totalCount;
226                                     if( choice <= 0.0 ) {
227                                             sgVec3 pos={x,z,y};
228                                             
229                                             buildCloud(cloud_def_root, 
230                                                        box_def_root, 
231                                                        tCloudVariety[i].name, 
232                                                        pos, 
233                                                        layer);
234                                             break;
235                                     }
236                             }
237                         }
238                 }
239         }
240
241         // Now we've built any clouds, enable them and set the density (coverage)
242         //layer->setCoverage(coverage);
243         //layer->applyCoverage();
244         thesky->get_cloud_layer(iLayer)->set_enable3dClouds(clouds_3d_enabled);
245 }
246
247 // TODO:call this after real metar updates
248 void FGClouds::buildCloudLayers(void) {
249         SGPropertyNode *metar_root = fgGetNode("/environment", true);
250         
251         double wind_speed_kt     = metar_root->getDoubleValue("wind-speed-kt");
252         double temperature_degc  = metar_root->getDoubleValue("temperature-sea-level-degc");
253         double dewpoint_degc     = metar_root->getDoubleValue("dewpoint-sea-level-degc");
254         double pressure_mb              = metar_root->getDoubleValue("pressure-sea-level-inhg") * SG_INHG_TO_PA / 100.0;
255
256         double dewp = pow(10.0, 7.5 * dewpoint_degc / (237.7 + dewpoint_degc));
257         double temp = pow(10.0, 7.5 * temperature_degc / (237.7 + temperature_degc));
258         double rel_humidity = dewp * 100 / temp;
259
260         // formule d'Epsy, base d'un cumulus
261         double cumulus_base = 122.0 * (temperature_degc - dewpoint_degc);
262         double stratus_base = 100.0 * (100.0 - rel_humidity) * SG_FEET_TO_METER;
263
264         bool cu_seen = false;
265
266         for(int iLayer = 0 ; iLayer < thesky->get_cloud_layer_count(); iLayer++) {
267                 SGPropertyNode *cloud_root = fgGetNode("/environment/clouds/layer", iLayer, true);
268
269                 double alt_ft = cloud_root->getDoubleValue("elevation-ft");
270                 double alt_m = alt_ft * SG_FEET_TO_METER;
271                 string coverage = cloud_root->getStringValue("coverage");
272                 
273                 
274                 double coverage_norm = 0.0;
275                 if( coverage == "few" )
276                         coverage_norm = 2.0/8.0;        // <1-2
277                 else if( coverage == "scattered" )
278                         coverage_norm = 4.0/8.0;        // 3-4
279                 else if( coverage == "broken" )
280                         coverage_norm = 6.0/8.0;        // 5-7
281                 else if( coverage == "overcast" )
282                         coverage_norm = 8.0/8.0;        // 8
283
284                 string layer_type = "nn";
285                 if( coverage == "cirrus" ) {
286                         layer_type = "ci";
287                 } else if( alt_ft > 16500 ) {
288 //                      layer_type = "ci|cs|cc";
289                         layer_type = "ci";
290                 } else if( alt_ft > 6500 ) {
291 //                      layer_type = "as|ac|ns";
292                         layer_type = "ac";
293                         if( pressure_mb < 1005.0 && coverage_norm >= 0.5 )
294                                 layer_type = "ns";
295                 } else {
296 //                      layer_type = "st|cu|cb|sc";
297                         if( cumulus_base * 0.80 < alt_m && cumulus_base * 1.20 > alt_m ) {
298                                 // +/- 20% from cumulus probable base
299                                 layer_type = "cu";
300                         } else if( stratus_base * 0.80 < alt_m && stratus_base * 1.40 > alt_m ) {
301                                 // +/- 20% from stratus probable base
302                                 layer_type = "st";
303                         } else {
304                                 // above formulae is far from perfect
305                                 if ( alt_ft < 2000 )
306                                         layer_type = "st";
307                                 else if( alt_ft < 4500 )
308                                         layer_type = "cu";
309                                 else
310                                         layer_type = "sc";
311                         }
312                 }
313                 
314                 buildLayer(iLayer, layer_type, alt_m, coverage_norm);
315         }
316 }
317
318 // copy from FGMetarEnvironmentCtrl until better
319 void
320 FGClouds::update_metar_properties( const FGMetar *m )
321 {
322     int i;
323     int j;
324     double d;
325     char s[128];
326
327     fgSetString("/environment/metar/station-id", m->getId());
328     fgSetDouble("/environment/metar/min-visibility-m",
329                 m->getMinVisibility().getVisibility_m() );
330     fgSetDouble("/environment/metar/max-visibility-m",
331                 m->getMaxVisibility().getVisibility_m() );
332
333     const SGMetarVisibility *dirvis = m->getDirVisibility();
334     for (i = 0; i < 8; i++, dirvis++) {
335         const char *min = "/environment/metar/visibility[%d]/min-m";
336         const char *max = "/environment/metar/visibility[%d]/max-m";
337
338         d = dirvis->getVisibility_m();
339
340         snprintf(s, 128, min, i);
341         fgSetDouble(s, d);
342         snprintf(s, 128, max, i);
343         fgSetDouble(s, d);
344     }
345
346     fgSetInt("/environment/metar/base-wind-range-from",
347              m->getWindRangeFrom() );
348     fgSetInt("/environment/metar/base-wind-range-to",
349              m->getWindRangeTo() );
350     fgSetDouble("/environment/metar/base-wind-speed-kt",
351                 m->getWindSpeed_kt() );
352     fgSetDouble("/environment/metar/gust-wind-speed-kt",
353                 m->getGustSpeed_kt() );
354     fgSetDouble("/environment/metar/temperature-degc",
355                 m->getTemperature_C() );
356     fgSetDouble("/environment/metar/dewpoint-degc",
357                 m->getDewpoint_C() );
358     fgSetDouble("/environment/metar/rel-humidity-norm",
359                 m->getRelHumidity() );
360     fgSetDouble("/environment/metar/pressure-inhg",
361                 m->getPressure_inHg() );
362
363     vector<SGMetarCloud> cv = m->getClouds();
364     vector<SGMetarCloud>::const_iterator cloud;
365
366     // Load into both the METAR and environment properties to stop interpolation
367     const char *cl[] = {"/environment/metar/clouds/layer[%i]",
368                         "/environment/clouds/layer[%i]"};
369
370     for (j = 0; j < 2; j++)
371     {
372         for (i = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, i++) {
373             const char *coverage_string[5] = 
374                 { "clear", "few", "scattered", "broken", "overcast" };
375             const double thickness[5] = { 0, 65, 600,750, 1000};
376             int q;
377     
378             snprintf(s, 128, cl[j], i);
379             strncat(s, "/coverage", 128);
380             q = cloud->getCoverage();
381             fgSetString(s, coverage_string[q] );
382             
383             snprintf(s, 128, cl[j], i);
384             strncat(s, "/elevation-ft", 128);
385             fgSetDouble(s, cloud->getAltitude_ft() + station_elevation_ft);
386             
387             snprintf(s, 128, cl[j], i);
388             strncat(s, "/thickness-ft", 128);
389             fgSetDouble(s, thickness[q]);
390             
391             snprintf(s, 128, cl[j], i);
392             strncat(s, "/span-m", 128);
393             fgSetDouble(s, 40000.0);
394         }
395     
396         for (; i < FGEnvironmentMgr::MAX_CLOUD_LAYERS; i++) {
397             
398             
399             snprintf(s, 128, cl[j], i);
400             strncat(s, "/coverage", 128);
401             fgSetString(s, "clear");
402     
403             snprintf(s, 128, cl[j], i);
404             strncat(s, "/elevation-ft", 128);
405             fgSetDouble(s, -9999);
406     
407             snprintf(s, 128, cl[j], i);
408             strncat(s, "/thickness-ft", 128);
409             fgSetDouble(s, 0);
410     
411             snprintf(s, 128, cl[j], i);
412             strncat(s, "/span-m", 128);
413             fgSetDouble(s, 40000.0);
414         }
415     }
416
417     fgSetDouble("/environment/metar/rain-norm", m->getRain());
418     fgSetDouble("/environment/metar/hail-norm", m->getHail());
419     fgSetDouble("/environment/metar/snow-norm", m->getSnow());
420     fgSetBool("/environment/metar/snow-cover", m->getSnowCover());
421 }
422
423 void
424 FGClouds::update_env_config ()
425 {
426     fgSetupWind( fgGetDouble("/environment/metar/base-wind-range-from"),
427                  fgGetDouble("/environment/metar/base-wind-range-to"),
428                  fgGetDouble("/environment/metar/base-wind-speed-kt"),
429                  fgGetDouble("/environment/metar/gust-wind-speed-kt") );
430
431     fgDefaultWeatherValue( "visibility-m",
432                            fgGetDouble("/environment/metar/min-visibility-m") );
433 #if 0
434     set_temp_at_altitude( fgGetDouble("/environment/metar/temperature-degc"),
435                           station_elevation_ft );
436     set_dewpoint_at_altitude( fgGetDouble("/environment/metar/dewpoint-degc"),
437                               station_elevation_ft );
438 #endif
439     fgDefaultWeatherValue( "pressure-sea-level-inhg",
440                            fgGetDouble("/environment/metar/pressure-inhg") );
441 }
442
443
444 void FGClouds::setLayer( int iLayer, float alt_ft, const string& coverage, const string& layer_type ) {
445         double coverage_norm = 0.0;
446         if( coverage == "few" )
447                 coverage_norm = 2.0/8.0;        // <1-2
448         else if( coverage == "scattered" )
449                 coverage_norm = 4.0/8.0;        // 3-4
450         else if( coverage == "broken" )
451                 coverage_norm = 6.0/8.0;        // 5-7
452         else if( coverage == "overcast" )
453                 coverage_norm = 8.0/8.0;        // 8
454
455         buildLayer(iLayer, layer_type, station_elevation_ft + alt_ft * SG_FEET_TO_METER, coverage_norm);
456 }
457
458 void FGClouds::buildScenario( const string& scenario ) {
459         string fakeMetar="";
460         string station = fgGetString("/environment/metar/station-id", "XXXX");
461         
462         // fetch station elevation if exists
463         if( station == "XXXX" )
464             station_elevation_ft = fgGetDouble("/position/ground-elev-m", 0.0);
465         else {
466             const FGAirport* a = globals->get_airports()->search( station );
467             station_elevation_ft = (a ? a->getElevation() : 0.0);
468         }
469
470         for(int iLayer = 0 ; iLayer < thesky->get_cloud_layer_count(); iLayer++) {
471             thesky->get_cloud_layer(iLayer)
472                 ->setCoverage(SGCloudLayer::SG_CLOUD_CLEAR);
473             thesky->get_cloud_layer(iLayer)->get_layer3D()->clear();
474         }
475
476         station += " 011000Z ";
477         if( scenario == "Fair weather" ) {
478                 fakeMetar = "15003KT 12SM SCT033 FEW200 20/08 Q1015 NOSIG";
479                 //setLayer(0, 3300.0, "scattered", "cu");
480         } else if( scenario == "Thunderstorm" ) {
481                 fakeMetar = "15012KT 08SM TSRA SCT040 BKN070 20/12 Q0995";
482                 setLayer(0, 4000.0, "scattered", "cb");
483                 setLayer(1, 7000.0, "scattered", "ns");
484         } else 
485                 return;
486         FGMetar *m = new FGMetar( station + fakeMetar );
487         update_metar_properties( m );
488         update_env_config();
489         // propagate aloft tables
490         //_controller->reinit();
491
492         fgSetString("/environment/metar/last-metar", m->getData());
493         // TODO:de-activate real metar updates
494         if( scenario == "Fair weather" ) {
495                 fgSetString("/environment/clouds/layer[1]/coverage", "cirrus");
496         }
497 }
498
499
500 void FGClouds::build() {
501         string scenario = fgGetString("/environment/weather-scenario", "METAR");
502         
503 //        if(!rebuild_required && (scenario == last_scenario))
504 //            return;
505         
506         if( last_scenario == "none" ) {
507         // save clouds and weather conditions
508             SGPropertyNode *param = fgGetNode("/environment/config", true);
509             copyProperties( param, last_env_config );
510             param = fgGetNode("/environment/clouds", true);
511             copyProperties( param, last_env_clouds );
512         }
513                 
514         if( scenario == "METAR" ) {
515             string realMetar = fgGetString("/environment/metar/real-metar", "");
516
517             if( realMetar != "" ) {
518                 fgSetString("/environment/metar/last-metar", realMetar.c_str());
519                 FGMetar *m = new FGMetar( realMetar );
520                 update_metar_properties( m );
521                 update_env_config();
522                         // propagate aloft tables
523                 _controller->reinit();
524                 buildCloudLayers();
525             }
526         }
527         else if( scenario == "none" ) {
528         // restore clouds and weather conditions
529             SGPropertyNode *param = fgGetNode("/environment/config", true);
530             copyProperties( last_env_config, param );
531             param = fgGetNode("/environment/clouds", true);
532             copyProperties( last_env_clouds, param );
533             fgSetDouble("/environment/metar/rain-norm", 0.0);
534             fgSetDouble("/environment/metar/snow-norm", 0.0);
535 //          update_env_config();
536             // propagate aloft tables
537             _controller->reinit();
538             buildCloudLayers();
539         }
540         else {
541             buildScenario( scenario );
542             _controller->reinit();
543             buildCloudLayers();
544         }
545         
546         last_scenario = scenario;
547
548         if( snd_lightning == NULL )
549             init();
550 }
551
552 void FGClouds::set_3dClouds(bool enable)
553 {
554     if (enable != clouds_3d_enabled) {
555         clouds_3d_enabled = enable;
556
557         for(int iLayer = 0 ; iLayer < thesky->get_cloud_layer_count(); iLayer++) {
558             thesky->get_cloud_layer(iLayer)->set_enable3dClouds(enable);
559
560             if (!enable) {
561                 thesky->get_cloud_layer(iLayer)->get_layer3D()->clear();
562             }
563         }
564
565         if (enable) {
566             build();
567         }
568     }
569 }
570
571 bool FGClouds::get_3dClouds() const {
572     return clouds_3d_enabled;
573 }
574