######################################################## # routines to set up, transform and manage local weather # Thorsten Renk, June 2011 # thermal model by Patrice Poly, April 2010 ######################################################## # function purpose # # calc_geo to compute the latitude to meter conversion # calc_d_sq to compute a distance square in local Cartesian approximation # effect_volume_loop to check if the aircraft has entered an effect volume # assemble_effect_array to store the size of the effect volume array # add_vectors to add two vectors in polar coordinates # wind_altitude_interpolation to interpolate aloft winds in altitude # wind_interpolation to interpolate aloft winds in altitude and position # get_slowdown_fraction to compute the effect of boundary layer wind slowdown # interpolation_loop to continuously interpolate weather parameters between stations # thermal_lift_start to start the detailed thermal model # thermal_lift_loop to manage the detailed thermal lift model # thermal_lift_stop to end the detailed thermal lift model # wave_lift_start to start the detailed wave lift model # wave_lift_loop to manage the detailed wave lift model # wave_lift_stop to end the detailed wave lift model # effect_volume_start to manage parameters when an effect volume is entered # effect_volume_stop to manage parameters when an effect volume is left # ts_factor (helper function for thermal lift model) # tl_factor (helper function for thermal lift model) # calcLift_max to calculate the maximal available thermal lift for given altitude # calcLift to calculate the thermal lift at aircraft position # calcWaveLift to calculate wave lift at aircraft position # select_cloud_model to select a path to the cloud model, given the cloud type and subtype # create_cloud_vec to place a single cloud into an array to be written later # clear_all to remove all clouds, effect volumes and weather stations and stop loops # create_detailed_cumulus_cloud to place multiple cloudlets into a box based on a size parameter # create_cumulonimbus_cloud to place multiple cloudlets into a box # create_cumulonimbus_cloud_rain to place multiple cloudlets into a box and add a rain layer beneath # create_cumosys wrapper to place a convective cloud system based on terrain coverage # cumulus_loop to place 25 Cumulus clouds each frame # create_cumulus to place a convective cloud system based on terrain coverage # recreate_cumulus to respawn convective clouds as part of the convective dynamics algorithm # cumulus_exclusion_layer to create a layer with 'holes' left for thunderstorm placement # create_rise_clouds to create a barrier cloud system # create_streak to create a cloud streak # create_undulatus to create an undulating cloud pattern # create_layer to create a cloud layer with optional precipitation # create_hollow_layer to create a cloud layer in a hollow cylinder (better for performance) # create_cloudbox to create a sophisticated cumulus cloud with different textures (experimental) # terrain_presampling_start to initialize terrain presampling # terrain_presampling_loop to sample 25 terrain points per frame # terrain_presampling to sample terrain elevation at a random point within specified area # terrain_presampling_analysis to analyze terrain presampling results # wave_detection_loop to detect if and where wave lift should be placed (currently unfinished) # get_convective_altitude to determine the altitude at which a Cumulus cloud is placed # manage presampling to take proper action when a presampling call has been finished # set_wind_model_flag to convert the wind model string into an integer flag # set_texture_mix to determine the texture mix between smooth and rough cloud appearance # create_effect_volume to create an effect volume # set_weather_station to specify a weather station for interpolation # set_atmosphere_ipoint to specify an interpolation point for visibility, haze and shading in the atmosphere # set_wind_ipoint to set an aloft wind interpolation point # set_wind_ipoint_metar to set a wind interpolation point from available ground METAR info where aloft is modelled # showDialog to pop up a dialog window # readFlags to read configuration flags from the property tree into Nasal variables at startup # streak_wrapper wrapper to execute streak from menu # convection wrapper wrapper to execute convective clouds from menu # barrier wrapper wrapper to execute barrier clouds from menu # single_cloud_wrapper wrapper to create single cloud from menu # layer wrapper wrapper to create layer from menu # box wrapper wrapper to create a cloudbox (experimental) # set_aloft wrapper wrapper to create aloft winds from menu # set_tile to call a weather tile creation from menu # startup to prepare the package at startup # test to serve as a testbed for new functions # object purpose # weatherStation to store info about weather conditions # atmopshereIpoint to store info about haze and light propagation in the atmosphere # windIpoint to store an interpolation point of the windfield # effectVolume to store effect volume info and provide methods to move and time-evolve effect volumes # thermalLift to store thermal info and provide methods to move and time-evolve a thermal # waveLift to store wave info ################################### # geospatial helper functions ################################### var calc_geo = func(clat) { lon_to_m = math.cos(clat*math.pi/180.0) * lat_to_m; m_to_lon = 1.0/lon_to_m; weather_dynamics.lon_to_m = lon_to_m; weather_dynamics.m_to_lon = m_to_lon; } var calc_d_sq = func (lat1, lon1, lat2, lon2) { var x = (lat1 - lat2) * lat_to_m; var y = (lon1 - lon2) * lon_to_m; return (x*x + y*y); } ################################### # effect volume management loop ################################### var effect_volume_loop = func (index, n_active) { if (local_weather_running_flag == 0) {return;} var n = 25; var esize = n_effectVolumeArray; var viewpos = geo.aircraft_position(); var active_counter = n_active; var i_max = index + n; if (i_max > esize) {i_max = esize;} for (var i = index; i < i_max; i = i+1) { var e = effectVolumeArray[i]; var flag = 0; # default assumption is that we're not in the volume var ealt_min = e.alt_low * ft_to_m; var ealt_max = e.alt_high * ft_to_m; if ((viewpos.alt() > ealt_min) and (viewpos.alt() < ealt_max)) # we are in the correct alt range { # so we load geometry next var geometry = e.geometry; var elat = e.lat; var elon = e.lon; var rx = e.r1; if (geometry == 1) # we have a cylinder { var d_sq = calc_d_sq(viewpos.lat(), viewpos.lon(), elat, elon); if (d_sq < (rx*rx)) {flag =1;} } else if (geometry == 2) # we have an elliptic shape { # get orientation var ry = e.r2; var phi = e.phi; phi = phi * math.pi/180.0; # first get unrotated coordinates var xx = (viewpos.lon() - elon) * lon_to_m; var yy = (viewpos.lat() - elat) * lat_to_m; # then rotate to align with the shape var x = xx * math.cos(phi) - yy * math.sin(phi); var y = yy * math.cos(phi) + xx * math.sin(phi); # then check elliptic condition if ((x*x)/(rx*rx) + (y*y)/(ry*ry) <1) {flag = 1;} } else if (geometry == 3) # we have a rectangular shape { # get orientation var ry = e.r2; var phi = e.phi; phi = phi * math.pi/180.0; # first get unrotated coordinates var xx = (viewpos.lon() - elon) * lon_to_m; var yy = (viewpos.lat() - elat) * lat_to_m; # then rotate to align with the shape var x = xx * math.cos(phi) - yy * math.sin(phi); var y = yy * math.cos(phi) + xx * math.sin(phi); # then check rectangle condition if ((x>-rx) and (x-ry) and (y wind_altitude_array[8]) {var alt_wind = 0.99* wind_altitude_array[8];} else {var alt_wind = altitude;} for (var i = 0; i<9; i=i+1) {if (alt_wind < wind_altitude_array[i]) {break;}} #var altNodeMin = w.getChild("altitude",i-1); #var altNodeMax = w.getChild("altitude",i); #var vmin = altNodeMin.getNode("windspeed-kt").getValue(); #var vmax = altNodeMax.getNode("windspeed-kt").getValue(); var vmin = w.alt[i-1].v; var vmax = w.alt[i].v; #var dir_min = altNodeMin.getNode("wind-from-heading-deg").getValue(); #var dir_max = altNodeMax.getNode("wind-from-heading-deg").getValue(); var dir_min = w.alt[i-1].d; var dir_max = w.alt[i].d; var f = (alt_wind - wind_altitude_array[i-1])/(wind_altitude_array[i] - wind_altitude_array[i-1]); var res = add_vectors(dir_min, (1-f) * vmin, dir_max, f * vmax); return res; } ################################### # windfield spatial interpolation ################################### var wind_interpolation = func (lat, lon, alt) { var sum_norm = 0; var sum_wind = [0,0]; var wsize = size(windIpointArray); for (var i = 0; i < wsize; i=i+1) { var w = windIpointArray[i]; var wpos = geo.Coord.new(); wpos.set_latlon(w.lat,w.lon,1000.0); var ppos = geo.Coord.new(); ppos.set_latlon(lat,lon,1000.0); var d = ppos.distance_to(wpos); if (d <100.0) {d = 100.0;} # to prevent singularity at zero sum_norm = sum_norm + (1./d) * w.weight; var res = wind_altitude_interpolation(alt,w); sum_wind = add_vectors(sum_wind[0], sum_wind[1], res[0], (res[1]/d) * w.weight); # gradually fade in the interpolation weight of newly added points to # avoid sudden jumps if (w.weight < 1.0) {w.weight = w.weight + 0.02;} } sum_wind[1] = sum_wind[1] /sum_norm; return sum_wind; } ################################### # boundary layer computations ################################### var get_slowdown_fraction = func { var tile_index = getprop(lw~"tiles/tile[4]/tile-index"); var altitude_agl = getprop("/position/altitude-agl-ft"); var altitude = getprop("/position/altitude-ft"); if (presampling_flag == 0) { var base_layer_thickness = 600.0; var f_slow = 1.0/3.0; } else { var alt_median = alt_50_array[tile_index - 1]; var alt_difference = alt_median - (altitude - altitude_agl); var base_layer_thickness = 150.0; # get the boundary layer size dependent on terrain altitude above terrain median if (alt_difference > 0.0) # we're low and the boundary layer grows {var boundary_alt = base_layer_thickness + 0.3 * alt_difference;} else # the boundary layer shrinks {var boundary_alt = base_layer_thickness + 0.1 * alt_difference;} if (boundary_alt < 50.0){boundary_alt = 50.0;} if (boundary_alt > 3000.0) {boundary_alt = 3000.0;} # get the boundary effect as a function of bounday layer size var f_slow = 1.0 - (0.2 + 0.17 * math.ln(boundary_alt/base_layer_thickness)); } if (debug_output_flag == 1) { print("Boundary layer thickness: ",base_layer_thickness); print("Boundary layer slowdown: ", f_slow); } return f_slow; } ################################### # interpolation management loop ################################### var interpolation_loop = func { if (local_weather_running_flag == 0) {return;} var viewpos = geo.aircraft_position(); var vis_before = getprop(lwi~"visibility-m"); # if applicable, do some work for fps sampling if (fps_control_flag == 1) { fps_samples = fps_samples +1; fps_sum = fps_sum + getprop("/sim/frame-rate"); } # determine at which distance we no longer keep an interpolation point, needs to be larger for METAR since points are more scarce if (metar_flag == 1) {var distance_to_unload = 250000.0;} else {var distance_to_unload = 120000.0;} # if we can set environment without a reset, the loop can run a bit faster for smoother interpolation # so determine the suitable timing if (compat_layer.features.can_disable_environment == 1) {var interpolation_loop_time = 0.2; var vlimit = 1.01;} else {var interpolation_loop_time = 1.0; var vlimit = 1.05;} # get an inverse distance weighted average from all defined weather stations var sum_alt = 0.0; var sum_vis = 0.0; var sum_T = 0.0; var sum_p = 0.0; var sum_D = 0.0; var sum_norm = 0.0; var n_stations = size(weatherStationArray); for (var i = 0; i < n_stations; i=i+1) { s = weatherStationArray[i]; var stpos = geo.Coord.new(); stpos.set_latlon(s.lat,s.lon,0.0); var d = viewpos.distance_to(stpos); if (d <100.0) {d = 100.0;} # to prevent singularity at zero sum_norm = sum_norm + 1./d * s.weight; sum_alt = sum_alt + (s.alt/d) * s.weight; sum_vis = sum_vis + (s.vis/d) * s.weight; sum_T = sum_T + (s.T/d) * s.weight; sum_D = sum_D + (s.D/d) * s.weight; sum_p = sum_p + (s.p/d) * s.weight; # gradually fade in the interpolation weight of newly added stations to # avoid sudden jumps if (s.weight < 1.0) {s.weight = s.weight + 0.02;} # automatically delete stations out of range # take care not to unload if weird values appear for a moment # never unload if only one station left if ((d > distance_to_unload) and (d < (distance_to_unload + 20000.0)) and (n_stations > 1)) { if (debug_output_flag == 1) {print("Distance to weather station ", d, " m, unloading ...", i);} weatherStationArray = weather_tile_management.delete_from_vector(weatherStationArray,i); i = i-1; n_stations = n_stations -1; } } setprop(lwi~"station-number", i+1); var ialt = sum_alt/sum_norm; var vis = sum_vis/sum_norm; var p = sum_p/sum_norm; var D = sum_D/sum_norm; var T = sum_T/sum_norm; # get an inverse distance weighted average from all defined atmospheric condition points sum_norm = 0.0; var sum_vis_aloft = 0.0; var sum_vis_alt1 = 0.0; var sum_vis_ovcst = 0.0; var sum_ovcst = 0.0; var sum_ovcst_alt_low = 0.0; var sum_ovcst_alt_high = 0.0; var sum_scatt = 0.0; var sum_scatt_alt_low = 0.0; var sum_scatt_alt_high = 0.0; var n_iPoints = size(atmosphereIpointArray); for (var i = 0; i < n_iPoints; i=i+1) { a = atmosphereIpointArray[i]; var apos = geo.Coord.new(); apos.set_latlon(a.lat,a.lon,0.0); var d = viewpos.distance_to(apos); if (d <100.0) {d = 100.0;} # to prevent singularity at zero sum_norm = sum_norm + 1./d * a.weight; sum_vis_aloft = sum_vis_aloft + (a.vis_aloft/d) * a.weight; sum_vis_alt1 = sum_vis_alt1 + (a.vis_alt1/d) * a.weight; sum_vis_ovcst = sum_vis_ovcst + (a.vis_ovcst/d) * a.weight; sum_ovcst = sum_ovcst + (a.ovcst/d) * a.weight; sum_ovcst_alt_low = sum_ovcst_alt_low + (a.ovcst_alt_low/d) * a.weight; sum_ovcst_alt_high = sum_ovcst_alt_high + (a.ovcst_alt_high/d) * a.weight; sum_scatt = sum_scatt + (a.scatt/d) * a.weight; sum_scatt_alt_low = sum_scatt_alt_low + (a.scatt_alt_low/d) * a.weight; sum_scatt_alt_high = sum_scatt_alt_high + (a.scatt_alt_high/d) * a.weight; # gradually fade in the interpolation weight of newly added stations to # avoid sudden jumps if (a.weight < 1.0) {a.weight = a.weight + 0.02;} # automatically delete stations out of range # take care not to unload if weird values appear for a moment # never unload if only one station left if ((d > distance_to_unload) and (d < (distance_to_unload + 20000.0)) and (n_iPoints > 1)) { if (debug_output_flag == 1) {print("Distance to atmosphere interpolation point ", d, " m, unloading ...", i);} atmosphereIpointArray = weather_tile_management.delete_from_vector(atmosphereIpointArray,i); i = i-1; n_iPoints = n_iPoints -1; } } setprop(lwi~"atmosphere-ipoint-number", i+1); var vis_aloft = sum_vis_aloft/sum_norm; var vis_alt1 = sum_vis_alt1/sum_norm; var vis_ovcst = sum_vis_ovcst/sum_norm; var ovcst_max = sum_ovcst/sum_norm; var ovcst_alt_low = sum_ovcst_alt_low/sum_norm; var ovcst_alt_high = sum_ovcst_alt_high/sum_norm; var scatt_max = sum_scatt/sum_norm; var scatt_alt_low = sum_scatt_alt_low/sum_norm; var scatt_alt_high = sum_scatt_alt_high/sum_norm; # altitude model for visibility - increase above the lowest inversion layer to simulate ground haze var altitude = getprop("position/altitude-ft"); current_mean_terrain_elevation = ialt; var alt1 = vis_alt1; var alt2 = alt1 + 1500.0; #var inc1 = 0.15; #var inc2 = 5.0; #var inc3 = 0.7; # compute the visibility gradients var inc1 = 0.1 * (vis_aloft - vis)/(vis_alt1 - ialt); var inc2 = 0.9 * (vis_aloft - vis)/1500.0; var inc3 = (vis_ovcst - vis_aloft)/(ovcst_alt_high - vis_alt1+1500); var inc4 = 0.5; # compute the visibility if (altitude < alt1) {vis = vis + inc1 * altitude;} else if (altitude < alt2) { vis = vis + inc1 * alt1 + inc2 * (altitude - alt1); } else if (altitude < ovcst_alt_high) { vis = vis + inc1 * alt1 + inc2 * (alt2-alt1) + inc3 * (altitude - alt2); } else if (altitude > ovcst_alt_high) { vis = vis + inc1 * alt1 + inc2 * (alt2-alt1) + inc3 * (ovcst_alt_high - alt2) + inc4 * (altitude - ovcst_alt_high); } # compute the horizon shading if (altitude < scatt_alt_low) { var scatt = scatt_max; } else if (altitude < scatt_alt_high) { var scatt = scatt_max + (0.95 - scatt_max) * (altitude - scatt_alt_low)/(scatt_alt_high-scatt_alt_low); } else {var scatt = 0.95;} # compute the overcast haze if (altitude < ovcst_alt_low) { var ovcst = ovcst_max; } else if (altitude < ovcst_alt_high) { var ovcst = ovcst_max - ovcst_max * (altitude - ovcst_alt_low)/(ovcst_alt_high-ovcst_alt_low); } else {var ovcst = 0.0;} # limit relative changes of the visibility, will make for gradual transitions if (vis/vis_before > vlimit) {vis = vlimit * vis_before;} else if (vis/vis_before < (2.0-vlimit)) {vis = (2.0-vlimit) * vis_before;} # write all properties into the weather interpolation record in the property tree setprop(lwi~"mean-terrain-altitude-ft",ialt); if (vis > 0.0) {setprop(lwi~"visibility-m",vis);} # a redundancy check setprop(lwi~"temperature-degc",T); setprop(lwi~"dewpoint-degc",D); if (p > 10.0) {setprop(lwi~"pressure-sea-level-inhg",p);} setprop(lwi~"turbulence",0.0); compat_layer.setScattering(scatt); compat_layer.setOvercast(ovcst); # now check if an effect volume writes the property and set only if not flag = getprop("local-weather/effect-volumes/number-active-vis"); if ((flag ==0) and (vis > 0.0) and (getprop(lw~"lift-loop-flag") == 0) and (compat_layer.smooth_visibility_loop_flag == 0)) { setprop(lw~"current/visibility-m",vis); compat_layer.setVisibility(vis); } flag = getprop("local-weather/effect-volumes/number-active-turb"); if ((flag ==0)) { setprop(lw~"current/turbulence",0.0); compat_layer.setTurbulence(0.0); } flag = getprop("local-weather/effect-volumes/number-active-lift"); if (flag ==0) { setprop(lw~"current/thermal-lift",0.0); } # no need to check for these, as they are not modelled in effect volumes setprop(lw~"current/temperature-degc",T); compat_layer.setTemperature(T); setprop(lw~"current/dewpoint-degc", D); compat_layer.setDewpoint(D); if (p>0.0) { setprop(lw~"current/pressure-sea-level-inhg",p); compat_layer.setPressure(p); } # now determine the local wind #var tile_index = props.globals.getNode(lw~"tiles").getChild("tile",4).getNode("tile-index").getValue(); var tile_index = getprop(lw~"tiles/tile[4]/tile-index"); if (wind_model_flag ==1) # constant { var winddir = weather_dynamics.tile_wind_direction[0]; var windspeed = weather_dynamics.tile_wind_speed[0]; } else if (wind_model_flag ==2) # constant in tile { var winddir = weather_dynamics.tile_wind_direction[tile_index-1]; var windspeed = weather_dynamics.tile_wind_speed[tile_index-1]; } else if (wind_model_flag ==3) # aloft interpolated, constant in tiles { var w = windIpointArray[0]; var res = wind_altitude_interpolation(altitude,w); var winddir = res[0]; var windspeed = res[1]; } else if (wind_model_flag == 5) # aloft waypoint interpolated { var res = wind_interpolation(viewpos.lat(), viewpos.lon(), altitude); var winddir = res[0]; var windspeed = res[1]; } # now do the boundary layer computations var altitude_agl = getprop("/position/altitude-agl-ft"); if (presampling_flag == 0) { var boundary_alt = 600.0; var windspeed_ground = windspeed/3.0; var f_min = 2.0/3.0; if (altitude_agl < boundary_alt) {var windspeed_current = windspeed_ground + 2.0 * windspeed_ground * (altitude_agl/boundary_alt);} else {var windspeed_current = windspeed;} } else { var alt_median = alt_50_array[tile_index - 1]; var alt_difference = alt_median - (altitude - altitude_agl); var base_layer_thickness = 150.0; # get the boundary layer size dependent on terrain altitude above terrain median if (alt_difference > 0.0) # we're low and the boundary layer grows {var boundary_alt = base_layer_thickness + 0.3 * alt_difference;} else # the boundary layer shrinks {var boundary_alt = base_layer_thickness + 0.1 * alt_difference;} if (boundary_alt < 50.0){boundary_alt = 50.0;} if (boundary_alt > 3000.0) {boundary_alt = 3000.0;} # get the boundary effect as a function of bounday layer size var f_min = 0.2 + 0.17 * math.ln(boundary_alt/base_layer_thickness); if (altitude_agl < boundary_alt) { var windspeed_current = (1-f_min) * windspeed + f_min * windspeed * (altitude_agl/boundary_alt); } else {var windspeed_current = windspeed;} } # determine gusts and turbulence in the bounday layer var gust_frequency = getprop(lw~"tmp/gust-frequency-hz"); if (gust_frequency > 0.0) { var gust_relative_strength = getprop(lw~"tmp/gust-relative-strength"); var gust_angvar = getprop(lw~"tmp/gust-angular-variation-deg"); var winddir_last = getprop(lwi~"wind-from-heading-deg"); var alt_scaling_factor = 1.2 * windspeed / 10.0; if (alt_scaling_factor < 1.0) {alt_scaling_factor = 1.0;} # expected mean number of gusts in time interval (should be < 1) var p_gust = gust_frequency * interpolation_loop_time; winddir_change = 0.0; if (rand() < p_gust) # we change the offsets for windspeed and direction { var alt_fact = 1.0 - altitude_agl/(boundary_alt * alt_scaling_factor); if (alt_fact < 0.0) {alt_fact = 0.0}; windspeed_multiplier = (1.0 + ((rand()) * gust_relative_strength * alt_fact)); winddir_change = alt_fact * (1.0 - 2.0 * rand()) * gust_angvar; winddir_change = winddir_change * 0.2; # Markov chain parameter, max. change per frame is 1/5 # if the Markov chain reaches the boundary, reflect #print("Winddir: ", winddir, " winddir_last: ", winddir_last, " winddir_change: ", winddir_change); if (weather_tile_management.relangle(winddir_last + winddir_change, winddir) > gust_angvar) {winddir_change = -winddir_change;} } windspeed_current = windspeed_current * windspeed_multiplier; winddir = winddir_last + winddir_change; } compat_layer.setWindSmoothly(winddir, windspeed_current); setprop(lwi~"wind-from-heading-deg", winddir); setprop(lwi~"wind-speed-kt",windspeed_current); setprop(lw~"current/wind-from-heading-deg",winddir); setprop(lw~"current/wind-speed-kt",windspeed_current); if (getprop(lw~"interpolation-loop-flag") ==1) {settimer(interpolation_loop, interpolation_loop_time);} } ################################### # thermal lift loop startup ################################### var thermal_lift_start = func (ev) { # copy the properties from effect volume to the lift object l = thermalLift.new(ev.lat, ev.lon, ev.radius, ev.height, ev.cn, ev.sh, ev.max_lift, ev.f_lift_radius); l.index = ev.index; if (dynamics_flag == 1) { l.timestamp = weather_dynamics.time_lw; if (dynamical_convection_flag == 1) { l.flt = ev.flt; l.evolution_timestamp = ev.evolution_timestamp; } } thermal = l; if (debug_output_flag == 1) { print("Entering thermal lift..."); print("strength: ", thermal.max_lift, " radius: ", thermal.radius); if (dynamical_convection_flag ==1) {print("fractional lifetime: ", thermal.flt);} } # and start the lift loop, unless another one is already running # so we block overlapping calls if (getprop(lw~"lift-loop-flag") == 0) {setprop(lw~"lift-loop-flag",1); settimer(thermal_lift_loop,0);} } ################################### # thermal lift loop ################################### var thermal_lift_loop = func { if (local_weather_running_flag == 0) {return;} var apos = geo.aircraft_position(); var tlat = thermal.lat; var tlon = thermal.lon; var tpos = geo.Coord.new(); tpos.set_latlon(tlat,tlon,0.0); var d = apos.distance_to(tpos); var alt = getprop("position/altitude-ft"); if (dynamical_convection_flag == 1) {var flt = thermal.flt;} else {var flt = 0.5;} var lift = calcLift(d, alt, thermal.radius, thermal.height, thermal.cn, thermal.sh, thermal.max_lift, thermal.f_lift_radius, flt); if (getprop(lw~"wave-loop-flag") ==1) { lift = lift + getprop(lw~"current/wave-lift"); } # compute a reduction in visibility when entering the cloudbase var vis = getprop(lw~"interpolation/visibility-m"); if (alt > 0.9 * thermal.height) { var visibility_reduction = math.pow((alt - 0.9 * thermal.height)/(0.2 * thermal.height),0.1); visibility_reduction = visibility_reduction * (1.0 - math.pow(d/(0.8*thermal.radius),14)); if (visibility_reduction > 1.0) {visibility_reduction = 1.0;} # this shouldn't ever happen if (visibility_reduction < 0.0) {visibility_reduction = 0.0;} vis = vis * (1.0 - 0.98 * visibility_reduction); } setprop(lw~"current/visibility-m",vis); compat_layer.setVisibility(vis); setprop(lw~"current/thermal-lift",lift); compat_layer.setLift(lift); # if dynamics is on, move the thermal and occasionally compute altitude and age if (dynamics_flag == 1) { thermal.move(); if ((rand() < 0.01) and (presampling_flag == 1)) # check every 100 frames { if (dynamical_convection_flag == 1) { thermal.correct_altitude_and_age(); if (thermal.flt > 1.1) {thermal_lift_stop();} } else { thermal.correct_altitude(); } } } if (getprop(lw~"lift-loop-flag") ==1) {settimer(thermal_lift_loop, 0);} } ################################### # thermal lift loop stop ################################### var thermal_lift_stop = func { setprop(lw~"lift-loop-flag",0); setprop(lw~"current/thermal-lift",0.0); compat_layer.setLift(0.0); if (debug_output_flag == 1) { print("Leaving thermal lift..."); } } ################################### # wave lift loop startup ################################### var wave_lift_start = func (ev) { # copy the properties from effect volume to the wave object w = waveLift.new (ev.lat, ev.lon, ev.r1, ev.r2, ev.phi, ev.height, ev.max_lift); w.index = ev.index; wave = w; # and start the lift loop, unless another one is already running # so we block overlapping calls if (getprop(lw~"wave-loop-flag") == 0) {setprop(lw~"wave-loop-flag",1); settimer(wave_lift_loop,0);} } ################################### # wave lift loop ################################### var wave_lift_loop = func { if (local_weather_running_flag == 0) {return;} var lat = getprop("position/latitude-deg"); var lon = getprop("position/longitude-deg"); var alt = getprop("position/altitude-ft"); var phi = wave.phi * math.pi/180.0; var xx = (lon - wave.lon) * lon_to_m; var yy = (lat - wave.lat) * lat_to_m; var x = xx * math.cos(phi) - yy * math.sin(phi); var y = yy * math.cos(phi) + xx * math.sin(phi); var lift = calcWaveLift(x,y,alt); # check if we are in a thermal, if so set wave lift and let the thermal lift loop add that if (getprop(lw~"lift-loop-flag") ==1) { setprop(lw~"current/wave-lift",lift); } else { setprop(lw~"current/thermal-lift",lift); } if (getprop(lw~"wave-loop-flag") ==1) {settimer(wave_lift_loop, 0);} } ################################### # wave lift loop stop ################################### var wave_lift_stop = func { setprop(lw~"wave-loop-flag",0); setprop(lw~"current/thermal-lift",0.0); } #################################### # action taken when in effect volume #################################### var effect_volume_start = func (ev) { var cNode = props.globals.getNode(lw~"current"); if (ev.vis_flag ==1) { # first store the current setting in case we need to restore on leaving var vis = ev.vis; ev.vis_r = cNode.getNode("visibility-m").getValue(); # then set the new value in current and execute change cNode.getNode("visibility-m").setValue(vis); #compat_layer.setVisibility(vis); compat_layer.setVisibilitySmoothly(vis); # then count the number of active volumes on entry (we need that to determine # what to do on exit) ev.n_entry_vis = getprop(lw~"effect-volumes/number-active-vis"); # and add to the counter setprop(lw~"effect-volumes/number-active-vis",getprop(lw~"effect-volumes/number-active-vis")+1); } if (ev.rain_flag == 1) { var rain = ev.rain; ev.rain_r = cNode.getNode("rain-norm").getValue(); cNode.getNode("rain-norm").setValue(rain); compat_layer.setRain(rain); ev.n_entry_rain = getprop(lw~"effect-volumes/number-active-rain"); setprop(lw~"effect-volumes/number-active-rain",getprop(lw~"effect-volumes/number-active-rain")+1); } if (ev.snow_flag == 1) { var snow = ev.snow; ev.snow_r = cNode.getNode("snow-norm").getValue(); cNode.getNode("snow-norm").setValue(snow); compat_layer.setSnow(snow); ev.n_entry_snow = getprop(lw~"effect-volumes/number-active-snow"); setprop(lw~"effect-volumes/number-active-snow",getprop(lw~"effect-volumes/number-active-snow")+1); } if (ev.turb_flag == 1) { var turbulence = ev.turb; ev.turb_r = cNode.getNode("turbulence").getValue(); cNode.getNode("turbulence").setValue(turbulence); compat_layer.setTurbulence(turbulence); ev.n_entry_turb = getprop(lw~"effect-volumes/number-active-turb"); setprop(lw~"effect-volumes/number-active-turb",getprop(lw~"effect-volumes/number-active-turb")+1); } if (ev.sat_flag == 1) { var saturation = ev.sat; ev.sat_r = getprop("/rendering/scene/saturation"); compat_layer.setLightSmoothly(saturation); ev.n_entry_sat = getprop(lw~"effect-volumes/number-active-sat"); setprop(lw~"effect-volumes/number-active-sat",getprop(lw~"effect-volumes/number-active-sat")+1); } if (ev.lift_flag == 1) { var lift = ev.lift; ev.lift_r = cNode.getNode("thermal-lift").getValue(); cNode.getNode("thermal-lift").setValue(lift); compat_layer.setLift(lift); ev.n_entry_lift = getprop(lw~"effect-volumes/number-active-lift"); setprop(lw~"effect-volumes/number-active-lift",getprop(lw~"effect-volumes/number-active-lift")+1); } else if (ev.lift_flag == 2) { ev.lift_r = cNode.getNode("thermal-lift").getValue(); ev.n_entry_lift = getprop(lw~"effect-volumes/number-active-lift"); setprop(lw~"effect-volumes/number-active-lift",getprop(lw~"effect-volumes/number-active-lift")+1); thermal_lift_start(ev); } else if (ev.lift_flag == 3) { ev.lift_r = cNode.getNode("thermal-lift").getValue(); ev.n_entry_lift = getprop(lw~"effect-volumes/number-active-lift"); setprop(lw~"effect-volumes/number-active-lift",getprop(lw~"effect-volumes/number-active-lift")+1); wave_lift_start(ev); } } var effect_volume_stop = func (ev) { var cNode = props.globals.getNode(lw~"current"); if (ev.vis_flag == 1) { var n_active = getprop(lw~"effect-volumes/number-active-vis"); var n_entry = ev.n_entry_vis; # if no other nodes affecting property are active, restore to outside # else restore settings as they have been when entering the volume when the number # of active volumes is the same as on entry (i.e. volumes are nested), otherwise # leave property at current because new definitions are already active and should not # be cancelled if (n_active ==1){var vis = props.globals.getNode(lw~"interpolation/visibility-m").getValue();} else if ((n_active -1) == n_entry) #{var vis = ev.getNode("restore/visibility-m").getValue();} {var vis = ev.vis_r;} else {var vis = cNode.getNode("visibility-m").getValue();} cNode.getNode("visibility-m").setValue(vis); #compat_layer.setVisibility(vis); compat_layer.setVisibilitySmoothly(vis); # and subtract from the counter setprop(lw~"effect-volumes/number-active-vis",getprop(lw~"effect-volumes/number-active-vis")-1); } if (ev.rain_flag == 1) { var n_active = getprop(lw~"effect-volumes/number-active-rain"); var n_entry = ev.n_entry_rain; if (n_active ==1){var rain = props.globals.getNode(lw~"interpolation/rain-norm").getValue();} else if ((n_active -1) == n_entry)# {var rain = ev.getNode("restore/rain-norm").getValue();} {var rain = ev.rain_r;} else {var rain = cNode.getNode("rain-norm").getValue();} cNode.getNode("rain-norm").setValue(rain); compat_layer.setRain(rain); setprop(lw~"effect-volumes/number-active-rain",getprop(lw~"effect-volumes/number-active-rain")-1); } if (ev.snow_flag == 1) { var n_active = getprop(lw~"effect-volumes/number-active-snow"); var n_entry = ev.n_entry_snow; if (n_active ==1){var snow = props.globals.getNode(lw~"interpolation/snow-norm").getValue();} else if ((n_active -1) == n_entry) {var snow = ev.snow_r;} else {var snow = cNode.getNode("snow-norm").getValue();} cNode.getNode("snow-norm").setValue(snow); compat_layer.setSnow(snow); setprop(lw~"effect-volumes/number-active-snow",getprop(lw~"effect-volumes/number-active-snow")-1); } if (ev.turb_flag == 1) { var n_active = getprop(lw~"effect-volumes/number-active-turb"); var n_entry = ev.n_entry_turb; if (n_active ==1){var turbulence = props.globals.getNode(lw~"interpolation/turbulence").getValue();} else if ((n_active -1) == n_entry) {var turbulence = ev.turb_r;} else {var turbulence = cNode.getNode("turbulence").getValue();} cNode.getNode("turbulence").setValue(turbulence); compat_layer.setTurbulence(turbulence); setprop(lw~"effect-volumes/number-active-turb",getprop(lw~"effect-volumes/number-active-turb")-1); } if (ev.sat_flag == 1) { var n_active = getprop(lw~"effect-volumes/number-active-sat"); var n_entry = ev.n_entry_sat; if (n_active ==1){var saturation = 1.0;} else if ((n_active -1) == n_entry) {var saturation = ev.sat_r;} else {var saturation = getprop("/rendering/scene/saturation");} compat_layer.setLightSmoothly(saturation); setprop(lw~"effect-volumes/number-active-sat",getprop(lw~"effect-volumes/number-active-sat")-1); } if (ev.lift_flag == 1) { var n_active = getprop(lw~"effect-volumes/number-active-lift"); var n_entry = ev.n_entry_lift; if (n_active ==1){var lift = props.globals.getNode(lw~"interpolation/thermal-lift").getValue();} else if ((n_active -1) == n_entry) {var lift = ev.lift_r;} else {var lift = cNode.getNode("thermal-lift").getValue();} cNode.getNode("thermal-lift").setValue(lift); compat_layer.setLift(lift); setprop(lw~"effect-volumes/number-active-lift",getprop(lw~"effect-volumes/number-active-lift")-1); } else if (ev.lift_flag == 2) { thermal_lift_stop(); setprop(lw~"effect-volumes/number-active-lift",getprop(lw~"effect-volumes/number-active-lift")-1); } else if (ev.lift_flag == 3) { wave_lift_stop(); setprop(lw~"effect-volumes/number-active-lift",getprop(lw~"effect-volumes/number-active-lift")-1); } } ######################################### # compute thermal lift in detailed model ######################################### var ts_factor = func (t, alt, height) { var t1 = 0.1; # fractional time at which lift is fully developed var t2 = 0.9; # fractional time at which lift starts to decay var t3 = 1.0; # fractional time at which lift is gone # no time dependence modelled yet # return 1.0; var t_a = t - (alt/height) * t1 - t1; if (t_a<0) {return 0.0;} else if (t_a 200.0*m_to_ft) and (alt < height)) {return max_lift;} # decreasing lift from cloudbase to 10% above base else if ((alt > height ) and (alt < height*1.1)) {return max_lift * 0.5 * (1.0 - math.cos((1.0-10.0*(alt-height)/height)*math.pi));} # no lift available above else {return 0.0;} } var calcLift = func (d, alt, R, height, cn, sh, max_lift, f_lift_radius, t) { # radius of slice at given altitude var r_total = (cn + alt/height*(1.0-cn)) * (R - R * (1.0- sh ) * (1.0 - ((2.0*alt/height)-1.0)*((2.0*alt/height)-1.0))); # print("r_total: ", r_total, "d: ",d); # print("alt: ", alt, "height: ",height); # no lift if we're outside the radius or above the thermal if ((d > r_total) or (alt > 1.1*height)) { return 0.0; } # fraction of radius providing lift var r_lift = f_lift_radius * r_total; # print("r_lift: ", r_lift); # if we are in the sink portion, get the max. sink for this time and altitude and adjust for actual position if ((d < r_total ) and (d > r_lift)) { var s_max = 0.5 * calcLift_max(alt, max_lift, height) * ts_factor(t, alt, height); # print("s_max: ", s_max); return s_max * math.sin(math.pi * (1.0 + (d-r_lift) * (1.0/(r_total - r_lift)))); } # else we are in the lift portion, get the max. lift for this time and altitude and adjust for actual position else { var l_max = calcLift_max(alt, max_lift, height) * tl_factor(t, alt, height); # print("l_max: ", l_max); return l_max * math.cos(math.pi * (d/(2.0 * r_lift))); } } ######################################### # compute wave lift in detailed model ######################################### var calcWaveLift = func (x,y, alt) { var lift = wave.max_lift * math.cos((y/wave.y) * 1.5 * math.pi); if (abs(x)/wave.x > 0.9) { lift = lift * (abs(x) - 0.9 * wave.x)/(0.1 * wave.x); } lift = lift * 2.71828 * math.exp(-alt/wave.height) * alt/wave.height; var alt_agl = getprop("/position/altitude-agl-ft"); if (alt_agl < 1000.0) { lift = lift * (alt_agl/1000.0) * (alt_agl/1000.0); } return lift; } ########################################################### # select a cloud model ########################################################### var select_cloud_model = func(type, subtype) { var rn = rand(); var path="Models/Weather/blank.ac"; if (type == "Cumulus"){ if (subtype == "small") { if (rn > 0.8) {path = "Models/Weather/cumulus_small_shader1.xml";} else if (rn > 0.6) {path = "Models/Weather/cumulus_small_shader2.xml";} else if (rn > 0.4) {path = "Models/Weather/cumulus_small_shader3.xml";} else if (rn > 0.2) {path = "Models/Weather/cumulus_small_shader4.xml";} else {path = "Models/Weather/cumulus_small_shader5.xml";} } else if (subtype == "large") { if (rn > 0.83) {path = "Models/Weather/cumulus_shader1.xml";} else if (rn > 0.664) {path = "Models/Weather/cumulus_shader2.xml";} else if (rn > 0.498) {path = "Models/Weather/cumulus_shader3.xml";} else if (rn > 0.332) {path = "Models/Weather/cumulus_shader4.xml";} else if (rn > 0.166) {path = "Models/Weather/cumulus_shader5.xml";} else {path = "Models/Weather/cumulus_shader6.xml";} } } else if (type == "Cumulus (cloudlet)"){ if (subtype == "small") { if (rn > 0.875) {path = "Models/Weather/cumulus_small_sl1.xml";} else if (rn > 0.750) {path = "Models/Weather/cumulus_small_sl2.xml";} else if (rn > 0.625) {path = "Models/Weather/cumulus_small_sl3.xml";} else if (rn > 0.500) {path = "Models/Weather/cumulus_small_sl4.xml";} else if (rn > 0.375) {path = "Models/Weather/cumulus_small_sl5.xml";} else if (rn > 0.250) {path = "Models/Weather/cumulus_small_sl6.xml";} else if (rn > 0.125) {path = "Models/Weather/cumulus_small_sl7.xml";} else {path = "Models/Weather/cumulus_small_sl8.xml";} } else if (subtype == "large") { if (rn > 0.9) {path = "Models/Weather/cumulus_sl1.xml";} else if (rn > 0.8) {path = "Models/Weather/cumulus_sl2.xml";} else if (rn > 0.7) {path = "Models/Weather/cumulus_sl3.xml";} else if (rn > 0.6) {path = "Models/Weather/cumulus_sl4.xml";} else if (rn > 0.5) {path = "Models/Weather/cumulus_sl5.xml";} else if (rn > 0.4) {path = "Models/Weather/cumulus_sl6.xml";} else if (rn > 0.3) {path = "Models/Weather/cumulus_sl7.xml";} else if (rn > 0.2) {path = "Models/Weather/cumulus_sl8.xml";} else if (rn > 0.1) {path = "Models/Weather/cumulus_sl9.xml";} else {path = "Models/Weather/cumulus_sl10.xml";} } } else if (type == "Congestus"){ if (subtype == "small") { if (rn > 0.9) {path = "Models/Weather/cumulus_sl1.xml";} else if (rn > 0.8) {path = "Models/Weather/cumulus_sl2.xml";} else if (rn > 0.7) {path = "Models/Weather/cumulus_sl3.xml";} else if (rn > 0.6) {path = "Models/Weather/cumulus_sl4.xml";} else if (rn > 0.5) {path = "Models/Weather/cumulus_sl5.xml";} else if (rn > 0.4) {path = "Models/Weather/cumulus_small_sl4.xml";} else if (rn > 0.3) {path = "Models/Weather/cumulus_small_sl5.xml";} else if (rn > 0.2) {path = "Models/Weather/cumulus_small_sl6.xml";} else if (rn > 0.1) {path = "Models/Weather/cumulus_small_sl7.xml";} else {path = "Models/Weather/cumulus_small_sl8.xml";} } else if (subtype == "large") { if (rn > 0.8) {path = "Models/Weather/congestus_sl1.xml";} else if (rn > 0.6) {path = "Models/Weather/congestus_sl2.xml";} else if (rn > 0.4) {path = "Models/Weather/congestus_sl3.xml";} else if (rn > 0.2) {path = "Models/Weather/congestus_sl4.xml";} else {path = "Models/Weather/congestus_sl5.xml";} } } else if (type == "Stratocumulus"){ if (subtype == "small") { if (rn > 0.8) {path = "Models/Weather/stratocumulus_small1.xml";} else if (rn > 0.6) {path = "Models/Weather/stratocumulus_small2.xml";} else if (rn > 0.4) {path = "Models/Weather/stratocumulus_small3.xml";} else if (rn > 0.2) {path = "Models/Weather/stratocumulus_small4.xml";} else {path = "Models/Weather/stratocumulus_small5.xml";} } else if (subtype == "large") { if (rn > 0.8) {path = "Models/Weather/stratocumulus_sl1.xml";} else if (rn > 0.6) {path = "Models/Weather/stratocumulus_sl2.xml";} else if (rn > 0.4) {path = "Models/Weather/stratocumulus_sl3.xml";} else if (rn > 0.2) {path = "Models/Weather/stratocumulus_sl4.xml";} else {path = "Models/Weather/stratocumulus_sl5.xml";} } } else if (type == "Cumulus (whisp)"){ if (subtype == "small") { if (rn > 0.8) {path = "Models/Weather/cumulus_whisp1.xml";} else if (rn > 0.6) {path = "Models/Weather/cumulus_whisp2.xml";} else if (rn > 0.4) {path = "Models/Weather/cumulus_whisp3.xml";} else if (rn > 0.2) {path = "Models/Weather/cumulus_whisp4.xml";} else {path = "Models/Weather/cumulus_whisp5.xml";} } else if (subtype == "large") { if (rn > 0.8) {path = "Models/Weather/cumulus_whisp1.xml";} else if (rn > 0.6) {path = "Models/Weather/cumulus_whisp2.xml";} else if (rn > 0.4) {path = "Models/Weather/cumulus_whisp3.xml";} else if (rn > 0.2) {path = "Models/Weather/cumulus_whisp4.xml";} else {path = "Models/Weather/cumulus_whisp5.xml";} } } else if (type == "Cumulus bottom"){ if (subtype == "small") { if (rn > 0.0) {path = "Models/Weather/cumulus_bottom1.xml";} } else if (subtype == "large") { if (rn > 0.0) {path = "Models/Weather/cumulus_bottom1.xml";} } } else if (type == "Congestus bottom"){ if (subtype == "small") { if (rn > 0.0) {path = "Models/Weather/congestus_bottom1.xml";} } else if (subtype == "large") { if (rn > 0.0) {path = "Models/Weather/congestus_bottom1.xml";} } } else if (type == "Stratocumulus bottom"){ if (subtype == "small") { if (rn > 0.0) {path = "Models/Weather/stratocumulus_bottom1.xml";} } else if (subtype == "large") { if (rn > 0.0) {path = "Models/Weather/stratocumulus_bottom1.xml";} } } else if (type == "Cumulonimbus (cloudlet)"){ if (subtype == "small") { if (rn > 0.8) {path = "Models/Weather/cumulonimbus_sl1.xml";} else if (rn > 0.6) {path = "Models/Weather/cumulonimbus_sl2.xml";} else if (rn > 0.4) {path = "Models/Weather/cumulonimbus_sl3.xml";} else if (rn > 0.2) {path = "Models/Weather/cumulonimbus_sl4.xml";} else {path = "Models/Weather/cumulonimbus_sl5.xml";} } else if (subtype == "large") { if (rn > 0.8) {path = "Models/Weather/cumulonimbus_sl1.xml";} else if (rn > 0.6) {path = "Models/Weather/cumulonimbus_sl2.xml";} else if (rn > 0.4) {path = "Models/Weather/cumulonimbus_sl3.xml";} else if (rn > 0.2) {path = "Models/Weather/cumulonimbus_sl4.xml";} else {path = "Models/Weather/cumulonimbus_sl5.xml";} } } else if (type == "Altocumulus"){ if (subtype == "small") { if (rn > 0.8) {path = "Models/Weather/altocumulus_shader6.xml";} else if (rn > 0.6) {path = "Models/Weather/altocumulus_shader7.xml";} else if (rn > 0.4) {path = "Models/Weather/altocumulus_shader8.xml";} else if (rn > 0.2) {path = "Models/Weather/altocumulus_shader9.xml";} else {path = "Models/Weather/altocumulus_shader10.xml";} } else if (subtype == "large") { if (rn > 0.8) {path = "Models/Weather/altocumulus_shader1.xml";} else if (rn > 0.6) {path = "Models/Weather/altocumulus_shader2.xml";} else if (rn > 0.4) {path = "Models/Weather/altocumulus_shader3.xml";} else if (rn > 0.2) {path = "Models/Weather/altocumulus_shader4.xml";} else {path = "Models/Weather/altocumulus_shader5.xml";} } } else if (type == "Stratus (structured)"){ if (subtype == "small") { if (rn > 0.8) {path = "Models/Weather/altocumulus_layer6.xml";} else if (rn > 0.6) {path = "Models/Weather/altocumulus_layer7.xml";} else if (rn > 0.4) {path = "Models/Weather/altocumulus_layer8.xml";} else if (rn > 0.2) {path = "Models/Weather/altocumulus_layer9.xml";} else {path = "Models/Weather/altocumulus_layer10.xml";} } else if (subtype == "large") { if (rn > 0.8) {path = "Models/Weather/altocumulus_layer1.xml";} else if (rn > 0.6) {path = "Models/Weather/altocumulus_layer2.xml";} else if (rn > 0.4) {path = "Models/Weather/altocumulus_layer3.xml";} else if (rn > 0.2) {path = "Models/Weather/altocumulus_layer4.xml";} else {path = "Models/Weather/altocumulus_layer5.xml";} } } else if (type == "Altocumulus perlucidus"){ if (subtype == "small") { if (rn > 0.8) {path = "Models/Weather/altocumulus_thinlayer6.xml";} else if (rn > 0.6) {path = "Models/Weather/altocumulus_thinlayer7.xml";} else if (rn > 0.4) {path = "Models/Weather/altocumulus_thinlayer8.xml";} else if (rn > 0.2) {path = "Models/Weather/altocumulus_thinlayer9.xml";} else {path = "Models/Weather/altocumulus_thinlayer10.xml";} } else if (subtype == "large") { if (rn > 0.8) {path = "Models/Weather/altocumulus_thinlayer1.xml";} else if (rn > 0.6) {path = "Models/Weather/altocumulus_thinlayer2.xml";} else if (rn > 0.4) {path = "Models/Weather/altocumulus_thinlayer3.xml";} else if (rn > 0.2) {path = "Models/Weather/altocumulus_thinlayer4.xml";} else {path = "Models/Weather/altocumulus_thinlayer5.xml";} } } else if ((type == "Cumulonimbus") or (type == "Cumulonimbus (rain)")) { if (subtype == "small") { if (rn > 0.5) {path = "Models/Weather/cumulonimbus_small1.xml";} else {path = "Models/Weather/cumulonimbus_small2.xml";} } else if (subtype == "large") { if (rn > 0.5) {path = "Models/Weather/cumulonimbus_small1.xml";} else {path = "Models/Weather/cumulonimbus_small2.xml";} } } else if (type == "Cirrus") { if (subtype == "large") { if (rn > 0.888) {path = "Models/Weather/cirrus1.xml";} else if (rn > 0.777) {path = "Models/Weather/cirrus2.xml";} else if (rn > 0.666) {path = "Models/Weather/cirrus3.xml";} else if (rn > 0.555) {path = "Models/Weather/cirrus4.xml";} else if (rn > 0.444) {path = "Models/Weather/cirrus5.xml";} else if (rn > 0.333) {path = "Models/Weather/cirrus6.xml";} else if (rn > 0.222) {path = "Models/Weather/cirrus7.xml";} else if (rn > 0.111) {path = "Models/Weather/cirrus8.xml";} else {path = "Models/Weather/cirrus9.xml";} } else if (subtype == "small") { if (rn > 0.75) {path = "Models/Weather/cirrus_amorphous1.xml";} else if (rn > 0.5) {path = "Models/Weather/cirrus_amorphous2.xml";} else if (rn > 0.25) {path = "Models/Weather/cirrus_amorphous3.xml";} else {path = "Models/Weather/cirrus_amorphous4.xml";} } } else if (type == "Cirrocumulus") { if (subtype == "small") { if (rn > 0.5) {path = "Models/Weather/cirrocumulus1.xml";} else {path = "Models/Weather/cirrocumulus2.xml";} } else if (subtype == "large") { if (rn > 0.875) {path = "Models/Weather/cirrocumulus1.xml";} else if (rn > 0.750){path = "Models/Weather/cirrocumulus4.xml";} else if (rn > 0.625){path = "Models/Weather/cirrocumulus5.xml";} else if (rn > 0.500){path = "Models/Weather/cirrocumulus6.xml";} else if (rn > 0.385){path = "Models/Weather/cirrocumulus7.xml";} else if (rn > 0.250){path = "Models/Weather/cirrocumulus8.xml";} else if (rn > 0.125){path = "Models/Weather/cirrocumulus9.xml";} else {path = "Models/Weather/cirrocumulus10.xml";} } } else if (type == "Cirrocumulus (cloudlet)") { if (subtype == "small") { if (rn > 0.8) {path = "Models/Weather/cirrocumulus_cloudlet6.xml";} else if (rn > 0.6) {path = "Models/Weather/cirrocumulus_cloudlet7.xml";} else if (rn > 0.4) {path = "Models/Weather/cirrocumulus_cloudlet8.xml";} else if (rn > 0.2) {path = "Models/Weather/cirrocumulus_cloudlet9.xml";} else {path = "Models/Weather/cirrocumulus_cloudlet10.xml";} } else if (subtype == "large") { if (rn > 0.8) {path = "Models/Weather/cirrocumulus_cloudlet1.xml";} else if (rn > 0.6) {path = "Models/Weather/cirrocumulus_cloudlet2.xml";} else if (rn > 0.4) {path = "Models/Weather/cirrocumulus_cloudlet3.xml";} else if (rn > 0.2) {path = "Models/Weather/cirrocumulus_cloudlet4.xml";} else {path = "Models/Weather/cirrocumulus_cloudlet5.xml";} } } else if (type == "Nimbus") { if (subtype == "small") { if (rn > 0.8) {path = "Models/Weather/nimbus_sls1.xml";} else if (rn > 0.6) {path = "Models/Weather/nimbus_sls2.xml";} else if (rn > 0.4) {path = "Models/Weather/nimbus_sls3.xml";} else if (rn > 0.2) {path = "Models/Weather/nimbus_sls4.xml";} else {path = "Models/Weather/nimbus_sls5.xml";} } else if (subtype == "large") { if (rn > 0.8) {path = "Models/Weather/nimbus_sl1.xml";} else if (rn > 0.6) {path = "Models/Weather/nimbus_sl2.xml";} else if (rn > 0.4) {path = "Models/Weather/nimbus_sl3.xml";} else if (rn > 0.2) {path = "Models/Weather/nimbus_sl4.xml";} else {path = "Models/Weather/nimbus_sl5.xml";} } } else if (type == "Stratus") { if (subtype == "small") { if (rn > 0.8) {path = "Models/Weather/stratus_layer1.xml";} else if (rn > 0.6) {path = "Models/Weather/stratus_layer2.xml";} else if (rn > 0.4) {path = "Models/Weather/stratus_layer3.xml";} else if (rn > 0.2) {path = "Models/Weather/stratus_layer4.xml";} else {path = "Models/Weather/stratus_layer5.xml";} } else if (subtype == "large") { if (rn > 0.8) {path = "Models/Weather/stratus_layer1.xml";} else if (rn > 0.6) {path = "Models/Weather/stratus_layer2.xml";} else if (rn > 0.4) {path = "Models/Weather/stratus_layer3.xml";} else if (rn > 0.2) {path = "Models/Weather/stratus_layer4.xml";} else {path = "Models/Weather/stratus_layer5.xml";} } } else if (type == "Stratus (thin)") { if (subtype == "small") { if (rn > 0.8) {path = "Models/Weather/stratus_tlayer1.xml";} else if (rn > 0.6) {path = "Models/Weather/stratus_tlayer2.xml";} else if (rn > 0.4) {path = "Models/Weather/stratus_tlayer3.xml";} else if (rn > 0.2) {path = "Models/Weather/stratus_tlayer4.xml";} else {path = "Models/Weather/stratus_tlayer5.xml";} } else if (subtype == "large") { if (rn > 0.8) {path = "Models/Weather/stratus_tlayer1.xml";} else if (rn > 0.6) {path = "Models/Weather/stratus_tlayer2.xml";} else if (rn > 0.4) {path = "Models/Weather/stratus_tlayer3.xml";} else if (rn > 0.2) {path = "Models/Weather/stratus_tlayer4.xml";} else {path = "Models/Weather/stratus_tlayer5.xml";} } } else if (type == "Cirrostratus") { if (subtype == "small") { if (rn > 0.75) {path = "Models/Weather/cirrostratus1.xml";} else if (rn > 0.5) {path = "Models/Weather/cirrostratus2.xml";} else if (rn > 0.25) {path = "Models/Weather/cirrostratus3.xml";} else {path = "Models/Weather/cirrostratus4.xml";} } else if (subtype == "large") { if (rn > 0.75) {path = "Models/Weather/cirrostratus1.xml";} else if (rn > 0.5) {path = "Models/Weather/cirrostratus2.xml";} else if (rn > 0.25) {path = "Models/Weather/cirrostratus3.xml";} else {path = "Models/Weather/cirrostratus4.xml";} } } else if (type == "Fog (thin)") { if (subtype == "small") { if (rn > 0.8) {path = "Models/Weather/stratus_thin1.xml";} else if (rn > 0.6) {path = "Models/Weather/stratus_thin2.xml";} else if (rn > 0.4) {path = "Models/Weather/stratus_thin3.xml";} else if (rn > 0.2) {path = "Models/Weather/stratus_thin4.xml";} else {path = "Models/Weather/stratus_thin5.xml";} } else if (subtype == "large") { if (rn > 0.8) {path = "Models/Weather/stratus_thin1.xml";} else if (rn > 0.6) {path = "Models/Weather/stratus_thin2.xml";} else if (rn > 0.4) {path = "Models/Weather/stratus_thin3.xml";} else if (rn > 0.2) {path = "Models/Weather/stratus_thin4.xml";} else {path = "Models/Weather/stratus_thin5.xml";} } } else if (type == "Fog (thick)") { if (subtype == "small") { if (rn > 0.8) {path = "Models/Weather/stratus_thick1.xml";} else if (rn > 0.6) {path = "Models/Weather/stratus_thick2.xml";} else if (rn > 0.4) {path = "Models/Weather/stratus_thick3.xml";} else if (rn > 0.2) {path = "Models/Weather/stratus_thick4.xml";} else {path = "Models/Weather/stratus_thick5.xml";} } else if (subtype == "large") { if (rn > 0.8) {path = "Models/Weather/stratus_thick1.xml";} else if (rn > 0.6) {path = "Models/Weather/stratus_thick2.xml";} else if (rn > 0.4) {path = "Models/Weather/stratus_thick3.xml";} else if (rn > 0.2) {path = "Models/Weather/stratus_thick4.xml";} else {path = "Models/Weather/stratus_thick5.xml";} } } else if (type == "Test") {path="Models/Weather/single_cloud.xml";} else if (type == "Box_test") { if (subtype == "standard") { if (rn > 0.8) {path = "Models/Weather/cloudbox1.xml";} else if (rn > 0.6) {path = "Models/Weather/cloudbox2.xml";} else if (rn > 0.4) {path = "Models/Weather/cloudbox3.xml";} else if (rn > 0.2) {path = "Models/Weather/cloudbox4.xml";} else {path = "Models/Weather/cloudbox5.xml";} } else if (subtype == "core") { if (rn > 0.8) {path = "Models/Weather/cloudbox_core1.xml";} else if (rn > 0.6) {path = "Models/Weather/cloudbox_core2.xml";} else if (rn > 0.4) {path = "Models/Weather/cloudbox_core3.xml";} else if (rn > 0.2) {path = "Models/Weather/cloudbox_core4.xml";} else {path = "Models/Weather/cloudbox_core5.xml";} } else if (subtype == "bottom") { if (rn > 0.66) {path = "Models/Weather/cloudbox_bottom1.xml";} else if (rn > 0.33) {path = "Models/Weather/cloudbox_bottom2.xml";} else if (rn > 0.0) {path = "Models/Weather/cloudbox_bottom3.xml";} } } else {print("Cloud type ", type, " subtype ",subtype, " not available!");} return path; } ########################################################### # place a single cloud into a vector to be processed # separately ########################################################### var create_cloud_vec = func(path, lat, long, alt, heading) { append(clouds_path,path); append(clouds_lat,lat); append(clouds_lon,long); append(clouds_alt,alt); append(clouds_orientation,heading); # globals (needed for Cumulus clouds) should be set if needed by the main cloud generating call if (dynamics_flag ==1) { append(clouds_mean_alt, cloud_mean_altitude); append(clouds_flt, cloud_fractional_lifetime); append(clouds_evolution_timestamp,cloud_evolution_timestamp); } } ########################################################### # clear all clouds and effects ########################################################### var clear_all = func { # clear the clouds and models var cloudNode = props.globals.getNode(lw~"clouds", 1); cloudNode.removeChildren("tile"); var modelNode = props.globals.getNode("models", 1).getChildren("model"); foreach (var m; modelNode) { var l = m.getNode("tile-index",1).getValue(); if (l != nil) { m.remove(); } } # reset pressure continuity weather_tiles.last_pressure = 0.0; # stop all loops setprop(lw~"effect-loop-flag",0); setprop(lw~"interpolation-loop-flag",0); setprop(lw~"tile-loop-flag",0); setprop(lw~"lift-loop-flag",0); setprop(lw~"wave-loop-flag",0); setprop(lw~"dynamics-loop-flag",0); setprop(lw~"timing-loop-flag",0); setprop(lw~"buffer-loop-flag",0); setprop(lw~"housekeeping-loop-flag",0); setprop(lw~"convective-loop-flag",0); weather_dynamics.convective_loop_kill_flag = 1; # long-running loop needs a different scheme to end # also remove rain, snow, haze and light effects compat_layer.setRain(0.0); compat_layer.setSnow(0.0); compat_layer.setLight(1.0); # set placement indices to zero setprop(lw~"clouds/placement-index",0); setprop(lw~"clouds/model-placement-index",0); setprop(lw~"effect-volumes/effect-placement-index",0); setprop(lw~"effect-volumes/number",0); setprop(lw~"tiles/tile-counter",0); # remove any quadtrees and arrays settimer ( func { setsize(weather_dynamics.cloudQuadtrees,0);},0.1); # to avoid error generation in this frame setsize(effectVolumeArray,0); n_effectVolumeArray = 0; # if we have used METAR, we may no longer want to do so metar_flag = 0; settimer ( func { setsize(weather_tile_management.modelArrays,0); setsize(weather_dynamics.tile_wind_direction,0); setsize(weather_dynamics.tile_wind_speed,0); setsize(weather_tile_management.cloudBufferArray,0); setsize(weather_tile_management.cloudSceneryArray,0); setsize(alt_20_array,0); setsize(alt_50_array,0); setsize(weather_dynamics.tile_convective_altitude,0); setsize(weather_dynamics.tile_convective_strength,0); setsize(weatherStationArray,0); setsize(windIpointArray,0); setsize(atmosphereIpointArray,0); setprop(lw~"clouds/buffer-count",0); setprop(lw~"clouds/cloud-scenery-count",0); weather_tile_management.n_cloudSceneryArray = 0; compat_layer.setScattering(0.8); compat_layer.setOvercast(0.0); setprop(lwi~"ipoint-number",0); setprop(lwi~"atmosphere-ipoint-number", 0); },1.1); setprop(lw~"tmp/presampling-status", "idle"); # reset the random store weather_tiles.rnd_store = rand(); # indicate that we are no longer running local_weather_running_flag = 0; } ########################################################### # detailed Cumulus clouds created from multiple cloudlets ########################################################### var create_detailed_cumulus_cloud = func (lat, lon, alt, size) { var edge_bias = convective_texture_mix; size = size + convective_size_bias; if (size > 2.0) { if (rand() > (size - 2.0)) {create_cumulonimbus_cloud(lat, lon, alt, size); } else {create_cumulonimbus_cloud_rain(lat, lon, alt, size, 0.1 + 0.2* rand());} return; } else if (size>1.5) { var type = "Congestus"; var btype = "Congestus bottom"; var height = 400; var n = 8; var n_b = 4; var x = 1000.0; var y = 300.0; var edge = 0.3; } else if (size>1.1) { var type = "Cumulus (cloudlet)"; var btype = "Cumulus bottom"; var height = 200; var n = 8; var n_b = 1; var x = 400.0; var y = 200.0; var edge = 0.3; } else if (size>0.8) { var type = "Cumulus (cloudlet)"; var height = 150; var n = 6; var x = 300.0; var y = 200.0; var edge = 0.3; } else if (size>0.4) { var type = "Cumulus (cloudlet)"; var btype = "Cumulus bottom"; var height = 100; var n = 4; var x = 200.0; var y = 200.0; var edge = 1.0; } else { var type = "Cumulus (whisp)"; var btype = "Cumulus bottom"; var height = 100; var n = 1; var x = 100.0; var y = 100.0; var edge = 1.0; } var alpha = rand() * 180.0; edge = edge + edge_bias; create_streak(type,lat,lon, alt+ 0.5* (height +cloud_vertical_size_map["Cumulus"] * ft_to_m), height,n,0.0,edge,x,1,0.0,0.0,y,alpha,1.0); # for large clouds, add a bottom if ((size > 1.1) and (edge < 0.4)) { create_streak(btype,lat,lon, alt, 100.0,n_b,0.0,edge,0.3*x,1,0.0,0.0,0.3*y,alpha,1.0); } } ########################################################### # detailed small Cumulonimbus clouds created from multiple cloudlets ########################################################### var create_cumulonimbus_cloud = func(lat, lon, alt, size) { var height = 3000.0; var alpha = rand() * 180.0; create_streak("Cumulonimbus",lat,lon, alt+ 700, 0,2,0.0,0.0,1600.0,1,0.0,0.0,800.0,alpha,1.0); create_streak("Cumulonimbus",lat,lon, alt+ 0.5* height + 700, height,6,0.0,0.0,1600.0,1,0.0,0.0,800.0,alpha,1.0); create_streak("Congestus bottom",lat,lon, alt, 100.0,6,0.0,1.0,0.7*1600,1,0.0,0.0,0.7*800,alpha,1.0); create_streak("Congestus bottom",lat,lon, alt + 700, 100.0,6,0.0,1.0,0.7*1600,1,0.0,0.0,0.7*800,alpha,1.0); } ########################################################### # detailed small Cumulonimbus and rain created from multiple cloudlets ########################################################### var create_cumulonimbus_cloud_rain = func(lat, lon, alt, size, rain) { var height = 3000.0; var alpha = rand() * 180.0; create_streak("Cumulonimbus",lat,lon, alt+ 700, 0,2,0.0,0.0,1600.0,1,0.0,0.0,800.0,alpha,1.0); create_streak("Cumulonimbus",lat,lon, alt+ 0.5* height + 700, height,6,0.0,0.0,1600.0,1,0.0,0.0,800.0,alpha,1.0); create_streak("Congestus bottom",lat,lon, alt, 100.0,6,0.0,1.0,0.7*1600,1,0.0,0.0,0.7*800,alpha,1.0); create_streak("Congestus bottom",lat,lon, alt + 700, 100.0,6,0.0,1.0,0.7*1600,1,0.0,0.0,0.7*800,alpha,1.0); # place a rain texture var path = "Models/Weather/rain2.xml"; if (thread_flag == 1) {create_cloud_vec(path, lat, lon, alt, 0.0);} else {compat_layer.create_cloud(path, lat, lon, alt, 0.0);} # and some rain underneath create_effect_volume(1, lat, lon, 2000.0, 2000.0, 0.0, 0.0, alt+1000.0, 8000.0 + 8000.0 * rand(), rain, -1, -1, -1 ,1,-1 ); } ########################################################### # wrappers for convective cloud system to distribute # call across several frames if needed ########################################################### var create_cumosys = func (blat, blon, balt, nc, size) { # realistic Cumulus has somewhat larger models, so compensate to get the same coverage if (detailed_clouds_flag == 1) {nc = int(0.7 * nc);} nc = int(nc / cumulus_efficiency_factor); if (thread_flag == 1) {setprop(lw~"tmp/convective-status", "computing"); cumulus_loop(blat, blon, balt, nc, size);} else {create_cumulus(blat, blon, balt, nc, size); if (debug_output_flag == 1) {print("Convective system done!");} } } var cumulus_loop = func (blat, blon, balt, nc, size) { if (local_weather_running_flag == 0) {return;} var n = int(25/cumulus_efficiency_factor); if (nc < 0) { if (debug_output_flag == 1) {print("Convective system done!");} setprop(lw~"tmp/convective-status", "idle"); assemble_effect_array(); convective_size_bias = 0.0; return; } create_cumulus(blat, blon, balt, n, size); settimer( func {cumulus_loop(blat, blon, balt, nc-n, size) },0); } ########################################################### # place a convective cloud system ########################################################### var create_cumulus = func (blat, blon, balt, nc, size) { var path = "Models/Weather/blank.ac"; var i = 0; var p = 0.0; var rn = 0.0; var place_lift_flag = 0; var strength = 0.0; var detail_flag = detailed_clouds_flag; var alpha = getprop(lw~"tmp/tile-orientation-deg") * math.pi/180.0; # the tile orientation if (detailed_terrain_interaction_flag == 1) { var alt_min = getprop(lw~"tmp/tile-alt-min-ft"); var alt_mean = getprop(lw~"tmp/tile-alt-mean-ft"); var alt_var = alt_mean - alt_min; } var sec_to_rad = 2.0 * math.pi/86400; # conversion factor for sinusoidal dependence on daytime calc_geo(blat); # get the local time of the day in seconds var t = getprop("sim/time/utc/day-seconds"); t = t + getprop("sim/time/local-offset"); # print("t is now:", t); # and make a simple sinusoidal model of thermal strength # daily variation in number of thermals, peaks at noon var t_factor1 = 0.5 * (1.0-math.cos((t * sec_to_rad))); # daily variation in strength of thermals, peaks around 15:30 var t_factor2 = 0.5 * (1.0-math.cos((t * sec_to_rad)-0.9)); # number of possible thermals equals overall strength times daily variation times geographic variation # this is a proxy for solar thermal energy nc = t_factor1 * nc * math.cos(blat/180.0*math.pi); var thermal_conditions = getprop(lw~"config/thermal-properties"); while (i < nc) { p = 0.0; place_lift_flag = 0; strength = 0.0; # pick a trial position inside the tile and rotate by tile orientation angle var x = (2.0 * rand() - 1.0) * size; var y = (2.0 * rand() - 1.0) * size; var lat = blat + (y * math.cos(alpha) - x * math.sin(alpha)) * m_to_lat; var lon = blon + (x * math.cos(alpha) + y * math.sin(alpha)) * m_to_lon; # now check ground cover type on chosen spot var info = geodinfo(lat, lon); if (info != nil) { var elevation = info[0] * m_to_ft; if (info[1] != nil){ var landcover = info[1].names[0]; if (contains(landcover_map,landcover)) {p = p + landcover_map[landcover];} else {print(p, " ", info[1].names[0]);} }} else {continue;} # apply some optional corrections, biases clouds towards higher elevations var terrain_altitude_factor = 1.0; if (detailed_terrain_interaction_flag == 1) { var elevation_enhancement = (elevation - alt_mean) / 1000.0; if (elevation_enhancement > 0.7) {elevation_enhancement = 0.7;} if (elevation_enhancement < -0.7) {elevation_enhancement = -0.7;} terrain_altitude_factor = 1.0 + elevation_enhancement; } # then decide if the thermal energy at the spot generates an updraft and a cloud if (rand() < (p * cumulus_efficiency_factor * terrain_altitude_factor)) # we decide to place a cloud at this spot { strength = (1.5 * rand() + (2.0 * p)) * t_factor2; # the strength of thermal activity at the spot if (strength > 1.0) { # we place a large cloud, and we generate lift path = select_cloud_model("Cumulus","large"); place_lift_flag = 1; } else {path = select_cloud_model("Cumulus","small");} # check if we have a terrain elevation analysis available and can use a # detailed placement altitude correction if (presampling_flag == 1) { var place_alt = get_convective_altitude(balt, elevation, getprop(lw~"tiles/tile-counter")); } else {var place_alt = balt;} cloud_mean_altitude = place_alt; cloud_fractional_lifetime = rand(); cloud_evolution_timestamp = weather_dynamics.time_lw; if (generate_thermal_lift_flag != 3) # no clouds if we produce blue thermals { if (thread_flag == 1) { if (detail_flag == 0){create_cloud_vec(path,lat,lon, place_alt, 0.0);} else {create_detailed_cumulus_cloud(lat, lon, place_alt, strength);} } else { if (detail_flag == 0){compat_layer.create_cloud(path, lat, lon, place_alt, 0.0);} else {create_detailed_cumulus_cloud(lat, lon, place_alt, strength);} } } # now see if we need to create a thermal - first check the flag if (generate_thermal_lift_flag == 1) # thermal by constant { # now check if convection is strong if (place_lift_flag == 1) { var lift = 3.0 + 10.0 * (strength -1.0); var radius = 500 + 500 * rand(); #print("Lift: ", lift * ft_to_m - 1.0); create_effect_volume(1, lat, lon, radius, radius, 0.0, 0.0, place_alt+500.0, -1, -1, -1, -1, lift, 1,-1); } # end if place_lift_flag } # end if generate-thermal-lift-flag else if ((generate_thermal_lift_flag == 2) or (generate_thermal_lift_flag == 3)) # thermal by function { if (place_lift_flag == 1) { var lift = (3.0 + 10.0 * (strength -1.0))/thermal_conditions; var radius = (500 + 500 * rand())*thermal_conditions; create_effect_volume(1, lat, lon, 1.1*radius, 1.1*radius, 0.0, 0.0, place_alt*1.15, -1, -1, -1, lift*0.04, lift, -2,-1); } # end if place_lift_flag } # end if generate-thermal-lift-flag } # end if rand < p i = i + 1; } # end while } ################################################################# # respawn convective clouds to compensate for decay # the difference being that new clouds get zero fractional # lifetime and are placed based on terrain with a different weight ################################################################## var recreate_cumulus = func (blat, blon, balt, alpha, nc, size, tile_index) { var path = "Models/Weather/blank.ac"; var i = 0; var p = 0.0; var rn = 0.0; var place_lift_flag = 0; var strength = 0.0; var detail_flag = detailed_clouds_flag; alpha = alpha * math.pi/180.0; # the tile orientation var sec_to_rad = 2.0 * math.pi/86400; # conversion factor for sinusoidal dependence on daytime # current aircraft position var alat = getprop("position/latitude-deg"); var alon = getprop("position/longitude-deg"); # get the local time of the day in seconds var t = getprop("sim/time/utc/day-seconds"); t = t + getprop("sim/time/local-offset"); # and make a simple sinusoidal model of thermal strength # daily variation in number of thermals, peaks at noon var t_factor1 = 0.5 * (1.0-math.cos((t * sec_to_rad))); # daily variation in strength of thermals, peaks around 15:30 var t_factor2 = 0.5 * (1.0-math.cos((t * sec_to_rad)-0.9)); # number of possible thermals equals overall strength times daily variation times geographic variation # this is a proxy for solar thermal energy nc = t_factor1 * nc * math.cos(blat/180.0*math.pi); var thermal_conditions = getprop(lw~"config/thermal-properties"); while (i < nc) { p = 0.0; place_lift_flag = 0; strength = 0.0; # pick a trial position inside the tile and rotate by tile orientation angle var x = (2.0 * rand() - 1.0) * size; var y = (2.0 * rand() - 1.0) * size; var lat = blat + (y * math.cos(alpha) - x * math.sin(alpha)) * m_to_lat; var lon = blon + (x * math.cos(alpha) + y * math.sin(alpha)) * m_to_lon; # check if the cloud would be spawned in visual range, if not don't bother var d_sq = calc_d_sq(alat, alon, lat, lon); if (math.sqrt(d_sq) > weather_tile_management.cloud_view_distance) {i = i+1; continue;} # now check ground cover type on chosen spot var info = geodinfo(lat, lon); if (info != nil) { var elevation = info[0] * m_to_ft; if (info[1] != nil){ var landcover = info[1].names[0]; if (contains(landcover_map,landcover)) {p = p + landcover_map[landcover];} else {print(p, " ", info[1].names[0]);} }} else {continue;} # check if to place a cloud with weight sqrt(p), the lifetime gets another sqrt(p) factor if (rand() > math.sqrt(p * cumulus_efficiency_factor)) {i=i+1; continue;} # then calculate the strength of the updraft strength = (1.5 * rand() + (2.0 * p)) * t_factor2; # the strength of thermal activity at the spot if (strength > 1.0) { path = select_cloud_model("Cumulus","large"); place_lift_flag = 1; } else {path = select_cloud_model("Cumulus","small");} if (presampling_flag == 1) { var place_alt = get_convective_altitude(balt, elevation, tile_index); } else {var place_alt = balt;} cloud_mean_altitude = place_alt; cloud_fractional_lifetime = 0.0; cloud_evolution_timestamp = weather_dynamics.time_lw; compat_layer.cloud_mean_altitude = place_alt; compat_layer.cloud_flt = cloud_fractional_lifetime; compat_layer.cloud_evolution_timestamp = cloud_evolution_timestamp; if (generate_thermal_lift_flag != 3) # no clouds if we produce blue thermals { if (thread_flag == 1) { thread_flag = 0; # create clouds immediately if (detail_flag == 0){compat_layer.create_cloud(path,lat,lon, place_alt, 0.0);} else {create_detailed_cumulus_cloud(lat, lon, place_alt, strength);} thread_flag = 1; # and restore threading } else { if (detail_flag == 0){compat_layer.create_cloud(path, lat, lon, place_alt, 0.0);} else {create_detailed_cumulus_cloud(lat, lon, place_alt, strength);} } } if (generate_thermal_lift_flag == 1) # thermal by constant { if (place_lift_flag == 1) { var lift = 3.0 + 10.0 * (strength -1.0); var radius = 500 + 500 * rand(); create_effect_volume(1, lat, lon, radius, radius, 0.0, 0.0, place_alt+500.0, -1, -1, -1, -1, lift, 1,-1); } # end if place_lift_flag } # end if generate-thermal-lift-flag else if ((generate_thermal_lift_flag == 2) or (generate_thermal_lift_flag == 3)) # thermal by function { if (place_lift_flag == 1) { var lift = (3.0 + 10.0 * (strength -1.0))/thermal_conditions; var radius = (500 + 500 * rand())*thermal_conditions; create_effect_volume(1, lat, lon, 1.1*radius, 1.1*radius, 0.0, 0.0, place_alt*1.15, -1, -1, -1, lift*0.04, lift, -2,-1); } # end if place_lift_flag } # end if generate-thermal-lift-flag i = i + 1; } # end while } ########################################################### # place a Cumulus layer with excluded regions # to avoid placing cumulus underneath a thunderstorm ########################################################### var cumulus_exclusion_layer = func (blat, blon, balt, n, size_x, size_y, alpha, s_min, s_max, n_ex, exlat, exlon, exrad) { var strength = 0; var flag = 1; var phi = alpha * math.pi/180.0; var detail_flag = detailed_clouds_flag; if (detail_flag == 1) {var i_max = int(0.25*n);} else {var i_max = int(1.0*n);} for (var i =0; i< i_max; i=i+1) { var x = (2.0 * rand() - 1.0) * size_x; var y = (2.0 * rand() - 1.0) * size_y; var lat = blat + (y * math.cos(phi) - x * math.sin(phi)) * m_to_lat; var lon = blon + (x * math.cos(phi) + y * math.sin(phi)) * m_to_lon; flag = 1; for (var j=0; j 1.0) {var path = select_cloud_model("Cumulus","large"); } else {var path = select_cloud_model("Cumulus","small");} if (thread_flag == 1) { if (detail_flag == 0){create_cloud_vec(path,lat,lon, balt, 0.0);} else {create_detailed_cumulus_cloud(lat, lon, balt, strength);} } else { if (detail_flag == 0){compat_layer.create_cloud(path, lat, lon, balt, 0.0);} else {create_detailed_cumulus_cloud(lat, lon, balt, strength);} } } # end if flag } # end for i } ########################################################### # place a barrier cloud system ########################################################### var create_rise_clouds = func (blat, blon, balt, nc, size, winddir, dist) { var path = "Models/Weather/blank.ac"; var i = 0; var p = 0.0; var rn = 0.0; var nsample = 10; var counter = 0; var dir = (winddir + 180.0) * math.pi/180.0; var step = dist/nsample; calc_geo(blat); while (i < nc) { counter = counter + 1; p = 0.0; var x = (2.0 * rand() - 1.0) * size; var y = (2.0 * rand() - 1.0) * size; var lat = blat + y * m_to_lat; var lon = blon + x * m_to_lon; var elevation = compat_layer.get_elevation(lat, lon); #print("elevation: ", elevation, "balt: ", balt); if ((elevation < balt) and (elevation != -1.0)) { for (var j = 0; j balt) { p = 1.0 - j * (1.0/nsample); #p = 1.0; break; } } } if (counter > 500) {print("Cannot place clouds - exiting..."); i = nc;} if (rand() < p) { path = select_cloud_model("Stratus (structured)","large"); compat_layer.create_cloud(path, lat, lon, balt, 0.0); counter = 0; i = i+1; } } # end while } ########################################################### # place a cloud streak ########################################################### var create_streak = func (type, blat, blong, balt, alt_var, nx, xoffset, edgex, x_var, ny, yoffset, edgey, y_var, direction, tri) { var flag = 0; var path = "Models/Weather/blank.ac"; calc_geo(blat); var dir = direction * math.pi/180.0; var ymin = -0.5 * ny * yoffset; var xmin = -0.5 * nx * xoffset; var xinc = xoffset * (tri-1.0) /ny; var jlow = int(nx*edgex); var ilow = int(ny*edgey); for (var i=0; i(nx-jlow-1))) and ((i(ny-ilow-1)))) # select a small or no cloud { if (rn > 2.0) {flag = 1;} else {path = select_cloud_model(type,"small");} } if ((j(nx-jlow-1)) or (i(ny-ilow-1))) { if (rn > 5.0) {flag = 1;} else {path = select_cloud_model(type,"small");} } else { # select a large cloud if (rn > 5.0) {flag = 1;} else {path = select_cloud_model(type,"large");} } if (flag==0){ if (thread_flag == 1) {create_cloud_vec(path, lat, long, alt, 0.0);} else {compat_layer.create_cloud(path, lat, long, alt, 0.0);} } } } } ########################################################### # place an undulatus pattern ########################################################### var create_undulatus = func (type, blat, blong, balt, alt_var, nx, xoffset, edgex, x_var, ny, yoffset, edgey, y_var, und_strength, direction, tri) { var flag = 0; var path = "Models/Weather/blank.ac"; calc_geo(blat); var dir = direction * math.pi/180.0; var ymin = -0.5 * ny * yoffset; var xmin = -0.5 * nx * xoffset; var xinc = xoffset * (tri-1.0) /ny; var jlow = int(nx*edgex); var ilow = int(ny*edgey); var und = 0.0; var und_array = []; for (var i=0; i(nx-jlow-1))) and ((i(ny-ilow-1)))) # select a small or no cloud { if (rn > 2.0) {flag = 1;} else {path = select_cloud_model(type,"small");} } if ((j(nx-jlow-1)) or (i(ny-ilow-1))) { if (rn > 5.0) {flag = 1;} else {path = select_cloud_model(type,"small");} } else { # select a large cloud if (rn > 5.0) {flag = 1;} else {path = select_cloud_model(type,"large");} } if (flag==0){ if (thread_flag == 1) {create_cloud_vec(path, lat, long, alt, 0.0);} else {compat_layer.create_cloud(path, lat, long, alt, 0.0);} } } } } ########################################################### # place a cloud layer ########################################################### var create_layer = func (type, blat, blon, balt, bthick, rx, ry, phi, density, edge, rainflag, rain_density) { var i = 0; var area = math.pi * rx * ry; var circ = math.pi * (rx + ry); # that's just an approximation var n = int(area/80000000.0 * 100 * density); var m = int(circ/63000.0 * 40 * rain_density); var path = "Models/Weather/blank.ac"; phi = phi * math.pi/180.0; if (contains(cloud_vertical_size_map, type)) {var alt_offset = cloud_vertical_size_map[type]/2.0 * m_to_ft;} else {var alt_offset = 0.0;} while(i ((1.0 - edge) * (1.0- edge))) { if (rand() > 0.4) { path = select_cloud_model(type,"small"); compat_layer.create_cloud(path, lat, lon, alt, 0.0); } } else { path = select_cloud_model(type,"large"); if (thread_flag == 1) {create_cloud_vec(path, lat, lon, alt, 0.0);} else {compat_layer.create_cloud(path, lat, lon, alt, 0.0);} } i = i + 1; } } i = 0; if (rainflag ==1){ while(i (gap_fraction * gap_fraction))) { var lat = blat + m_to_lat * (y * math.cos(phi) - x * math.sin(phi)); var lon = blon + m_to_lon * (x * math.cos(phi) + y * math.sin(phi)); if (res > ((1.0 - edge) * (1.0- edge))) { if (rand() > 0.4) { path = select_cloud_model(type,"small"); compat_layer.create_cloud(path, lat, lon, alt, 0.0); } } else { path = select_cloud_model(type,"large"); if (thread_flag == 1) {create_cloud_vec(path, lat, lon, alt, 0.0);} else {compat_layer.create_cloud(path, lat, lon, alt, 0.0);} } i = i + 1; } else # we are in the central gap region { i = i + 1; } } i = 0; } ########################################################### # place a cloud box ########################################################### var create_cloudbox = func (type, blat, blon, balt, dx,dy,dz,n, f_core, r_core, h_core, n_core, f_bottom, h_bottom, n_bottom) { var phi = 0; # first get core coordinates var core_dx = dx * f_core; var core_dy = dy * f_core; var core_dz = dz * h_core; var core_x_offset = (1.0 * rand() - 0.5) * ((dx - core_dx) * r_core); var core_y_offset = (1.0 * rand() - 0.5) * ((dy - core_dy) * r_core); # get the bottom geometry var bottom_dx = dx * f_bottom; var bottom_dy = dy * f_bottom; var bottom_dz = dz * h_bottom; var bottom_offset = 400.0; # in practice, need a small shift # fill the main body of the box for (var i=0; i core_x_offset - 0.5 * core_dx) and (x < core_x_offset + 0.5 * core_dx)) { if ((y > core_y_offset - 0.5 * core_dy) and (y < core_y_offset + 0.5 * core_dy)) { i = i -1; continue; } } var alt = balt + bottom_dz + bottom_offset + dz * rand(); var lat = blat + m_to_lat * (y * math.cos(phi) - x * math.sin(phi)); var lon = blon + m_to_lon * (x * math.cos(phi) + y * math.sin(phi)); var path = select_cloud_model(type,"standard"); if (thread_flag == 1) {create_cloud_vec(path, lat, lon, alt, 0.0);} else {compat_layer.create_cloud(path, lat, lon, alt, 0.0);} } # fill the core region for (var i=0; i 0.2) # we have below 20 fps {n = 5;} else if (dt > 0.1) # we have below 10 fps {n = 10;} else if (dt > 0.05) # we have below 5 fps {n = 15;} if (nc <= 0) # we're done and may analyze the result { terrain_presampling_analysis(); if (debug_output_flag == 1) {print("Presampling done!");} setprop(lw~"tmp/presampling-status", "finished"); return; } terrain_presampling(blat, blon, n, size, alpha); settimer( func {terrain_presampling_loop(blat, blon, nc-n_out, size, alpha) },0); } ########################################################### # terrain presampling routine ########################################################### var terrain_presampling = func (blat, blon, ntries, size, alpha) { var phi = alpha * math.pi/180.0; var elevation = 0.0; var lat_vec = []; var lon_vec = []; var lat_lon_vec = []; for (var i=0; i int(0.5 *n_tot)) {alt_med = i * 500.0; break;} } sum = 0; for (var i=0; i<20;i=i+1) { sum = sum + terrain_n[i]; if (sum > int(0.3 *n_tot)) {alt_20 = i * 500.0; break;} } for (var i=0; i<20;i=i+1) {alt_mean = alt_mean + terrain_n[i] * i * 500.0;} alt_mean = alt_mean/n_tot; for (var i=0; i<20;i=i+1) {if (terrain_n[i] > 0) {alt_min = i * 500.0; break;}} var n_max = 0; sum = 0; for (var i=0; i<19;i=i+1) { sum = sum + terrain_n[i]; if (terrain_n[i] > n_max) {n_max = terrain_n[i];} if ((n_max > terrain_n[i+1]) and (sum > int(0.3*n_tot))) {alt_low_min = i * 500; break;} } } else { #print("Hard-coded sampling..."); var n_tot = getprop("/environment/terrain/area[0]/input/max-samples"); var alt_mean = getprop("/environment/terrain/area[0]/output/alt-mean-ft"); var alt_med = getprop("/environment/terrain/area[0]/output/alt-median-ft"); var alt_min = getprop("/environment/terrain/area[0]/output/alt-min-ft"); var alt_20 = getprop("/environment/terrain/area[0]/output/alt-offset-ft"); } if (debug_output_flag == 1) {print("Terrain presampling analysis results:"); print("total: ",n_tot," mean: ",alt_mean," median: ",alt_med," min: ",alt_min, " alt_20: ", alt_20);} setprop(lw~"tmp/tile-alt-offset-ft",alt_20); setprop(lw~"tmp/tile-alt-median-ft",alt_med); setprop(lw~"tmp/tile-alt-min-ft",alt_min); setprop(lw~"tmp/tile-alt-mean-ft",alt_mean); setprop(lw~"tmp/tile-alt-layered-ft",0.5 * (alt_min + alt_20)); append(alt_50_array, alt_med); append(alt_20_array, alt_20); } ########################################################### # wave conditions search ########################################################### var wave_detection_loop = func (blat, blon, nx, alpha) { if (local_weather_running_flag == 0) {return;} var phi = alpha * math.pi/180.0; var elevation = 0.0; var ny = 20; for (var i=0; i 1.0) {fraction = 1.0;} # no placement above maximum shift if (fraction < 0.0) {fraction = 0.0;} # no downward shift # get the cloud base var cloudbase = balt - alt_offset; var alt_above_terrain = balt - elevation; # the shift strength is weakened if the layer is high above base elevation # the reference altitude is 1000 ft, anything higher has less sensitivity to terrain var shift_strength = 1000.0/alt_above_terrain; if (shift_strength > 1.0) {shift_strength = 1.0;} # no enhancement for very low layers if (shift_strength < 0.0) {shift_strength = 1.0;} # this shouldn't happen, but just in case... if (alt_diff > alt_variation) {alt_diff = alt_variation;} # maximal shift is given by alt_variation return balt + shift_strength * alt_diff * fraction; } ########################################################### # terrain presampling listener dispatcher ########################################################### var manage_presampling = func { var status = getprop(lw~"tmp/presampling-status"); # we only take action when the analysis is done if (status != "finished") {return;} if (getprop(lw~"tiles/tile-counter") == 0) # we deal with a tile setup call from the menu { set_tile(); } else # the tile setup call came from weather_tile_management { var lat = getprop(lw~"tiles/tmp/latitude-deg"); var lon = getprop(lw~"tiles/tmp/longitude-deg"); var code = getprop(lw~"tiles/tmp/code"); var dir_index = getprop(lw~"tiles/tmp/dir-index"); weather_tile_management.generate_tile(code, lat, lon, dir_index); } # set status to idle again setprop(lw~"tmp/presampling-status", "idle"); } ########################################################### # hardcoded terrain presampling listener dispatcher ########################################################### var manage_hardcoded_presampling = func { var status = getprop("/environment/terrain/area[0]/enabled"); print("Hard-coded terrain presampling status: ", status); # no action unless the sampler has finished if (status ==0) {return;} # no action if the sampler hasn't been started if (getprop(lw~"tmp/presampling-status") != "sampling") {return;} terrain_presampling_analysis(); if (debug_output_flag == 1) {print("Presampling done!");} setprop(lw~"tmp/presampling-status", "finished"); } ########################################################### # set wind model flag ########################################################### var set_wind_model_flag = func { var wind_model = getprop(lw~"config/wind-model"); if (wind_model == "constant") {wind_model_flag = 1;} else if (wind_model == "constant in tile") {wind_model_flag =2;} else if (wind_model == "aloft interpolated") {wind_model_flag =3; } else if (wind_model == "airmass interpolated") {wind_model_flag =4;} else if (wind_model == "aloft waypoints") {wind_model_flag =5;} else {print("Wind model not implemented!"); wind_model_flag =1;} } ########################################################### # set texture mix for convective clouds ########################################################### var set_texture_mix = func { var thermal_properties = getprop(lw~"config/thermal-properties"); convective_texture_mix = -(thermal_properties - 1.0) * 0.4; if (convective_texture_mix < -0.2) {convective_texture_mix = -0.2;} if (convective_texture_mix > 0.2) {convective_texture_mix = 0.2;} } ########################################################### # create an effect volume ########################################################### var create_effect_volume = func (geometry, lat, lon, r1, r2, phi, alt_low, alt_high, vis, rain, snow, turb, lift, lift_flag, sat) { var ev = effectVolume.new (geometry, lat, lon, r1, r2, phi, alt_low, alt_high, vis, rain, snow, turb, lift, lift_flag, sat); ev.index = getprop(lw~"tiles/tile-counter"); ev.active_flag = 0; if (vis < 0.0) {ev.vis_flag = 0;} else {ev.vis_flag = 1;} if (rain < 0.0) {ev.rain_flag = 0;} else {ev.rain_flag = 1;} if (snow < 0.0) {ev.snow_flag = 0;} else {ev.snow_flag = 1;} if (turb < 0.0) {ev.turb_flag = 0;} else {ev.turb_flag = 1;} if (lift_flag == 0.0) {ev.lift_flag = 0;} else {ev.lift_flag = 1;} if (sat < 0.0) {ev.sat_flag = 0;} else {ev.sat_flag = 1;} if (sat > 1.0) {sat = 1.0;} if (lift_flag == -2) # we create a thermal by function { ev.lift_flag = 2; ev.radius = 0.8 * r1; ev.height = alt_high * 0.87; ev.cn = 0.7 + rand() * 0.2; ev.sh = 0.7 + rand() * 0.2; ev.max_lift = lift; ev.f_lift_radius = 0.7 + rand() * 0.2; if (dynamics_flag == 1) # globals set by the convective system { ev.flt = cloud_fractional_lifetime; ev.evolution_timestamp = cloud_evolution_timestamp; } } if (lift_flag == -3) # we create a wave lift { ev.lift_flag = 3; ev.height = 10000.0; # scale height in ft ev.max_lift = lift; ev.index = 0; # static objects are assigned tile id zero } # set a timestamp if needed if (dynamics_flag == 1) { ev.timestamp = weather_dynamics.time_lw; } # and add to the counter setprop(lw~"effect-volumes/number",getprop(lw~"effect-volumes/number")+1); append(effectVolumeArray,ev); } ########################################################### # set a weather station for interpolation ########################################################### var set_weather_station = func (lat, lon, alt, vis, T, D, p) { var s = weatherStation.new (lat, lon, alt, vis, T, D, p); s.index = getprop(lw~"tiles/tile-counter"); s.weight = 0.02; # set a timestamp if needed if (dynamics_flag == 1) { s.timestamp = weather_dynamics.time_lw; } append(weatherStationArray,s); } ########################################################### # set an atmosphere condition point for interpolation ########################################################### var set_atmosphere_ipoint = func (lat, lon, vis_aloft, vis_alt1, vis_ovcst, ovcst,ovcst_alt_low, ovcst_alt_high, scatt, scatt_alt_low, scatt_alt_high) { var a = atmosphereIpoint.new (lat, lon, vis_aloft, vis_alt1, vis_ovcst, ovcst, ovcst_alt_low, ovcst_alt_high, scatt, scatt_alt_low, scatt_alt_high); a.index = getprop(lw~"tiles/tile-counter"); a.weight = 0.02; # set a timestamp if needed if (dynamics_flag == 1) { a.timestamp = weather_dynamics.time_lw; } append(atmosphereIpointArray,a); } ########################################################### # set a wind interpolation point ########################################################### var set_wind_ipoint = func (lat, lon, d0, v0, d1, v1, d2, v2, d3, v3, d4, v4, d5, v5, d6, v6, d7, v7, d8, v8) { var w = windIpoint.new(lat, lon, d0, v0, d1, v1, d2, v2, d3, v3, d4, v4, d5, v5, d6, v6, d7, v7, d8, v8); append(windIpointArray, w); } ########################################################### # set a wind interpolation point from ground METAR data ########################################################### var set_wind_ipoint_metar = func (lat, lon, d0, v0) { # insert a plausible pattern of aloft winds based on ground info # direction of Coriolis deflection depends on hemisphere if (lat >0.0) {var dsign = -1.0;} else {var dsign = 1.0;} var v1 = v0 * (1.0 + rand() * 0.1); var d1 = d0 + dsign * 2.0 * rand(); var v2 = v0 * (1.2 + rand() * 0.2); var d2 = d0 + dsign * (3.0 * rand() + 2.0); var v3 = v0 * (1.3 + rand() * 0.4) + 5.0; var d3 = d0 + dsign * (3.0 * rand() + dsign * 4.0); var v4 = v0 * (1.7 + rand() * 0.5) + 10.0; var d4 = d0 + dsign * (4.0 * rand() + dsign * 8.0); var v5 = v0 * (1.7 + rand() * 0.5) + 20.0; var d5 = d0 + dsign * (4.0 * rand() + dsign * 10.0); var v6 = v0 * (1.7 + rand() * 0.5) + 40.0; var d6 = d0 + dsign * (4.0 * rand() + dsign * 12.0); var v7 = v0 * (2.0 + rand() * 0.7) + 50.0; var d7 = d0 + dsign * (4.0 * rand() + dsign * 13.0); var v8 = v0 * (2.0 + rand() * 0.7) + 55.0;; var d8 = d0 + dsign * (5.0 * rand() + dsign * 14.0); var w = windIpoint.new(lat, lon, d0, v0, d1, v1, d2, v2, d3, v3, d4, v4, d5, v5, d6, v6, d7, v7, d8, v8); append(windIpointArray, w); } ########################################################### # helper to show additional dialogs ########################################################### var showDialog = func (name) { fgcommand("dialog-show", props.Node.new({"dialog-name":name})); } ########################################################### # helper to transfer configuration flags in menu to Nasal ########################################################### var readFlags = func { # thermal lift must be 1 for constant thermals (obsolete), 2 for thermals by model (menu default) # and 3 for blue thermals (set internally inside the tile only) if (getprop(lw~"config/generate-thermal-lift-flag") ==1) {generate_thermal_lift_flag = 2;} else {generate_thermal_lift_flag = 0}; thread_flag = getprop(lw~"config/thread-flag"); dynamics_flag = getprop(lw~"config/dynamics-flag"); presampling_flag = getprop(lw~"config/presampling-flag"); detailed_clouds_flag = getprop(lw~"config/detailed-clouds-flag"); dynamical_convection_flag = getprop(lw~"config/dynamical-convection-flag"); debug_output_flag = getprop(lw~"config/debug-output-flag"); fps_control_flag = getprop(lw~"config/fps-control-flag"); } ########################################################### # wrappers to call functions from the local weather menu bar ########################################################### var streak_wrapper = func { thread_flag = 0; dynamics_flag = 0; presampling_flag = 0; var array = []; append(weather_tile_management.modelArrays,array); setprop(lw~"tiles/tile-counter",getprop(lw~"tiles/tile-counter")+1); var lat = getprop("position/latitude-deg"); var lon = getprop("position/longitude-deg"); var type = getprop("/local-weather/tmp/cloud-type"); var alt = getprop("/local-weather/tmp/alt"); var nx = getprop("/local-weather/tmp/nx"); var xoffset = getprop("/local-weather/tmp/xoffset"); var xedge = getprop("/local-weather/tmp/xedge"); var ny = getprop("/local-weather/tmp/ny"); var yoffset = getprop("/local-weather/tmp/yoffset"); var yedge = getprop("/local-weather/tmp/yedge"); var dir = getprop("/local-weather/tmp/dir"); var tri = getprop("/local-weather/tmp/tri"); var rnd_alt = getprop("/local-weather/tmp/rnd-alt"); var rnd_pos_x = getprop("/local-weather/tmp/rnd-pos-x"); var rnd_pos_y = getprop("/local-weather/tmp/rnd-pos-y"); create_streak(type,lat,lon,alt,rnd_alt,nx,xoffset,xedge,rnd_pos_x,ny,yoffset,yedge,rnd_pos_y,dir,tri); } var convection_wrapper = func { thread_flag = 0; dynamics_flag = 0; presampling_flag = 0; var array = []; append(weather_tile_management.modelArrays,array); setprop(lw~"tiles/tile-counter",getprop(lw~"tiles/tile-counter")+1); var lat = getprop("position/latitude-deg"); var lon = getprop("position/longitude-deg"); var alt = getprop("/local-weather/tmp/conv-alt"); var size = getprop("/local-weather/tmp/conv-size"); var strength = getprop("/local-weather/tmp/conv-strength"); var n = int(10 * size * size * strength); create_cumosys(lat,lon,alt,n, size*1000.0); } var barrier_wrapper = func { thread_flag = 0; dynamics_flag = 0; presampling_flag = 0; var array = []; append(weather_tile_management.modelArrays,array); setprop(lw~"tiles/tile-counter",getprop(lw~"tiles/tile-counter")+1); var lat = getprop("position/latitude-deg"); var lon = getprop("position/longitude-deg"); var alt = getprop("/local-weather/tmp/bar-alt"); var n = getprop("/local-weather/tmp/bar-n"); var dir = getprop("/local-weather/tmp/bar-dir"); var dist = getprop("/local-weather/tmp/bar-dist") * 1000.0; var size = getprop("/local-weather/tmp/bar-size") * 1000.0; create_rise_clouds(lat, lon, alt, n, size, dir, dist); } var single_cloud_wrapper = func { thread_flag = 0; dynamics_flag = 0; presampling_flag = 0; var array = []; append(weather_tile_management.modelArrays,array); setprop(lw~"tiles/tile-counter",getprop(lw~"tiles/tile-counter")+1); var type = getprop("/local-weather/tmp/scloud-type"); var subtype = getprop("/local-weather/tmp/scloud-subtype"); var lat = getprop("/local-weather/tmp/scloud-lat"); var lon = getprop("/local-weather/tmp/scloud-lon"); var alt = getprop("/local-weather/tmp/scloud-alt"); var heading = getprop("/local-weather/tmp/scloud-dir"); var path = select_cloud_model(type,subtype); compat_layer.create_cloud(path, lat, lon, alt, heading); } var layer_wrapper = func { thread_flag = 0; dynamics_flag = 0; presampling_flag = 0; var array = []; append(weather_tile_management.modelArrays,array); setprop(lw~"tiles/tile-counter",getprop(lw~"tiles/tile-counter")+1); var lat = getprop("position/latitude-deg"); var lon = getprop("position/longitude-deg"); var type = getprop(lw~"tmp/layer-type"); var rx = getprop(lw~"tmp/layer-rx") * 1000.0; var ry = getprop(lw~"tmp/layer-ry") * 1000.0; var phi = getprop(lw~"tmp/layer-phi"); var alt = getprop(lw~"tmp/layer-alt"); var thick = getprop(lw~"tmp/layer-thickness"); var density = getprop(lw~"tmp/layer-density"); var edge = getprop(lw~"tmp/layer-edge"); var rain_flag = getprop(lw~"tmp/layer-rain-flag"); var rain_density = getprop(lw~"tmp/layer-rain-density"); create_layer(type, lat, lon, alt, thick, rx, ry, phi, density, edge, rain_flag, rain_density); } var box_wrapper = func { thread_flag = 0; dynamics_flag = 0; presampling_flag = 0; setprop(lw~"tiles/tile-counter",getprop(lw~"tiles/tile-counter")+1); var lat = getprop("position/latitude-deg"); var lon = getprop("position/longitude-deg"); var alt = getprop("position/altitude-ft"); var x = getprop(lw~"tmp/box-x-m"); var y = getprop(lw~"tmp/box-y-m"); var z = getprop(lw~"tmp/box-alt-ft"); var n = getprop(lw~"tmp/box-n"); var f_core = getprop(lw~"tmp/box-core-fraction"); var r_core = getprop(lw~"tmp/box-core-offset"); var h_core = getprop(lw~"tmp/box-core-height"); var n_core = getprop(lw~"tmp/box-core-n"); var f_bottom = getprop(lw~"tmp/box-bottom-fraction"); var h_bottom = getprop(lw~"tmp/box-bottom-thickness"); var n_bottom = getprop(lw~"tmp/box-bottom-n"); var type = "Box_test"; #create_cloudbox(type,subtype,lat, lon, alt, x,y,z,n); create_cloudbox(type, lat, lon, alt, x,y,z,n, f_core, r_core, h_core, n_core, f_bottom, h_bottom, n_bottom); } var set_aloft_wrapper = func { var lat = getprop(lw~"tmp/ipoint-latitude-deg"); var lon = getprop(lw~"tmp/ipoint-longitude-deg"); var d0 = getprop(lw~"tmp/FL0-wind-from-heading-deg"); var v0 = getprop(lw~"tmp/FL0-windspeed-kt"); var d1 = getprop(lw~"tmp/FL50-wind-from-heading-deg"); var v1 = getprop(lw~"tmp/FL50-windspeed-kt"); var d2 = getprop(lw~"tmp/FL100-wind-from-heading-deg"); var v2 = getprop(lw~"tmp/FL100-windspeed-kt"); var d3 = getprop(lw~"tmp/FL180-wind-from-heading-deg"); var v3 = getprop(lw~"tmp/FL180-windspeed-kt"); var d4 = getprop(lw~"tmp/FL240-wind-from-heading-deg"); var v4 = getprop(lw~"tmp/FL240-windspeed-kt"); var d5 = getprop(lw~"tmp/FL300-wind-from-heading-deg"); var v5 = getprop(lw~"tmp/FL300-windspeed-kt"); var d6 = getprop(lw~"tmp/FL340-wind-from-heading-deg"); var v6 = getprop(lw~"tmp/FL340-windspeed-kt"); var d7 = getprop(lw~"tmp/FL390-wind-from-heading-deg"); var v7 = getprop(lw~"tmp/FL390-windspeed-kt"); var d8 = getprop(lw~"tmp/FL450-wind-from-heading-deg"); var v8 = getprop(lw~"tmp/FL450-windspeed-kt"); set_wind_ipoint(lat, lon, d0, v0, d1, v1, d2, v2, d3, v3, d4, v4, d5, v5, d6, v6, d7, v7, d8, v8); if (wind_model_flag == 5) {setprop(lwi~"ipoint-number", getprop(lwi~"ipoint-number") + 1);} } #################################### # tile setup call wrapper #################################### var set_tile = func { # check if another instance of local weather is running already if (local_weather_running_flag == 1) { setprop("/sim/messages/pilot", "Local weather: Local weather is already running, use Clear/End before restarting. Aborting..."); return; } local_weather_startup_flag = 1; var type = getprop("/local-weather/tmp/tile-type"); # set tile center coordinates to current position var lat = getprop("position/latitude-deg"); var lon = getprop("position/longitude-deg"); setprop(lw~"tiles/tmp/latitude-deg",lat); setprop(lw~"tiles/tmp/longitude-deg",lon); setprop(lw~"tiles/tmp/dir-index",4); readFlags(); # check consistency of flags if (dynamical_convection_flag == 1) { if (dynamics_flag == 0) { print("Dynamical convection needs weather dynamics to run! Aborting..."); setprop("/sim/messages/pilot", "Local weather: dynamical convection needs weather dynamics to run! Aborting..."); return; } if (presampling_flag == 0) { print("Dynamical convection needs terrain presampling to run! Aborting..."); setprop("/sim/messages/pilot", "Local weather: dynamical convection needs terrain presampling to run! Aborting..."); return; } } # if we can do so, we switch global weather and METAR parsing in environment off at this point if (compat_layer.features.can_disable_environment ==1) { props.globals.getNode("/environment/config/enabled").setBoolValue(0); props.globals.getNode("/environment/params/metar-updates-environment").setBoolValue(0); } # switch off normal 3d clouds compat_layer.setDefaultCloudsOff(); # now see if we need to presample the terrain if ((presampling_flag == 1) and (getprop(lw~"tmp/presampling-status") == "idle")) { terrain_presampling_start(lat, lon, 1000, 40000, getprop(lw~"tmp/tile-orientation-deg")); return; } # indicate that we're up and running local_weather_startup_flag = 0; local_weather_running_flag = 1; # see if we use METAR for weather setup if ((getprop("/environment/metar/valid") == 1) and (getprop(lw~"tmp/tile-management") == "METAR")) { type = "METAR"; metar_flag = 1; setprop(lw~"METAR/station-id","METAR"); } else if ((getprop("/environment/metar/valid") == 0) and (getprop(lw~"tmp/tile-management") == "METAR")) { print("No METAR available, aborting..."); setprop("/sim/messages/pilot", "Local weather: No METAR available! Aborting..."); return; } # see if we need to create an aloft wind interpolation structure if ((wind_model_flag == 3) or ((wind_model_flag ==5) and (getprop(lwi~"ipoint-number") == 0))) { if (metar_flag != 1) {set_aloft_wrapper();} } # prepare the first tile wind field if (metar_flag == 1) # the winds from current METAR are used { # METAR reports ground winds, we want to set aloft, so we need to compute the local boundary layer # need to set the tile index for this setprop(lw~"tiles/tile[4]/tile-index",1); var boundary_correction = 1.0/get_slowdown_fraction(); var metar_base_wind_deg = getprop("environment/metar/base-wind-dir-deg"); var metar_base_wind_speed = boundary_correction * getprop("environment/metar/base-wind-speed-kt"); if ((wind_model_flag == 1) or (wind_model_flag == 2)) { append(weather_dynamics.tile_wind_direction, metar_base_wind_deg); append(weather_dynamics.tile_wind_speed, metar_base_wind_speed); setprop(lw~"tmp/tile-orientation-deg",metar_base_wind_deg); } else if (wind_model_flag == 5) { var station_lat = getprop("/environment/metar/station-latitude-deg"); var station_lon = getprop("/environment/metar/station-longitude-deg"); set_wind_ipoint_metar(station_lat, station_lon, metar_base_wind_deg, metar_base_wind_speed); var res = wind_interpolation(lat,lon,0.0); append(weather_dynamics.tile_wind_direction,res[0]); append(weather_dynamics.tile_wind_speed,res[1]); setprop(lw~"tmp/tile-orientation-deg", weather_dynamics.tile_wind_direction[0]); } else { print("Wind model currently not supported with live data!"); setprop("/sim/messages/pilot", "Local weather: Wind model currently not supported with live data! Aborting..."); return; } } else { if (wind_model_flag == 5) # it needs to be interpolated { var res = wind_interpolation(lat,lon,0.0); append(weather_dynamics.tile_wind_direction,res[0]); append(weather_dynamics.tile_wind_speed,res[1]); } else if (wind_model_flag == 3) # it comes from a different menu { append(weather_dynamics.tile_wind_direction, getprop(lw~"tmp/FL0-wind-from-heading-deg")); append(weather_dynamics.tile_wind_speed, getprop(lw~"tmp/FL0-windspeed-kt")); } else # it comes from the standard menu { append(weather_dynamics.tile_wind_direction, getprop(lw~"tmp/tile-orientation-deg")); append(weather_dynamics.tile_wind_speed, getprop(lw~"tmp/windspeed-kt")); } # when the aloft wind menu is used, the lowest winds should be taken from there # so we need to overwrite the setting from the tile generating menu in this case # otherwise the wrong orientation is built if (wind_model_flag ==3) { setprop(lw~"tmp/tile-orientation-deg", getprop(lw~"tmp/FL0-wind-from-heading-deg")); } else if (wind_model_flag == 5) { setprop(lw~"tmp/tile-orientation-deg", weather_dynamics.tile_wind_direction[0]); } } # create all the neighbouring tile coordinate sets weather_tile_management.create_neighbours(lat,lon,getprop(lw~"tmp/tile-orientation-deg")); setprop(lw~"tiles/tile-counter",getprop(lw~"tiles/tile-counter")+1); # see if we need to generate a quadtree structure for clouds if (dynamics_flag ==1) { var quadtree = []; weather_dynamics.generate_quadtree_structure(0, quadtree); append(weather_dynamics.cloudQuadtrees,quadtree); } if (type == "High-pressure-core") {weather_tiles.set_high_pressure_core_tile();} else if (type == "High-pressure") {weather_tiles.set_high_pressure_tile();} else if (type == "High-pressure-border") {weather_tiles.set_high_pressure_border_tile();} else if (type == "Low-pressure-border") {weather_tiles.set_low_pressure_border_tile();} else if (type == "Low-pressure") {weather_tiles.set_low_pressure_tile();} else if (type == "Low-pressure-core") {weather_tiles.set_low_pressure_core_tile();} else if (type == "Cold-sector") {weather_tiles.set_cold_sector_tile();} else if (type == "Warm-sector") {weather_tiles.set_warm_sector_tile();} else if (type == "Tropical") {weather_tiles.set_tropical_weather_tile();} else if (type == "Coldfront") {weather_tiles.set_coldfront_tile();} else if (type == "Warmfront") {weather_tiles.set_warmfront1_tile();} else if (type == "Warmfront-2") {weather_tiles.set_warmfront2_tile();} else if (type == "Warmfront-3") {weather_tiles.set_warmfront3_tile();} else if (type == "Warmfront-4") {weather_tiles.set_warmfront4_tile();} else if (type == "METAR") {weather_tiles.set_METAR_tile();} else if (type == "Altocumulus sky") {weather_tiles.set_altocumulus_tile();setprop(lw~"tiles/code","altocumulus_sky");} else if (type == "Broken layers") {weather_tiles.set_broken_layers_tile();setprop(lw~"tiles/code","broken_layers");} else if (type == "Cold front") {weather_tiles.set_coldfront_tile();setprop(lw~"tiles/code","coldfront");} else if (type == "Cirrus sky") {weather_tiles.set_cirrus_sky_tile();setprop(lw~"tiles/code","cirrus_sky");} else if (type == "Fair weather") {setprop(lw~"tiles/code","cumulus_sky");weather_tiles.set_fair_weather_tile();} else if (type == "Glider's sky") {setprop(lw~"tiles/code","gliders_sky");weather_tiles.set_gliders_sky_tile();} else if (type == "Blue thermals") {setprop(lw~"tiles/code","blue_thermals");weather_tiles.set_blue_thermals_tile();} else if (type == "Incoming rainfront") {weather_tiles.set_rainfront_tile();setprop(lw~"tiles/code","rainfront");} else if (type == "8/8 stratus sky") {weather_tiles.set_overcast_stratus_tile();setprop(lw~"tiles/code","overcast_stratus");} else if (type == "Test tile") {weather_tiles.set_4_8_stratus_tile();setprop(lw~"tiles/code","test");} else if (type == "Summer rain") {weather_tiles.set_summer_rain_tile();setprop(lw~"tiles/code","summer_rain");} else {print("Tile not implemented.");setprop(lw~"tiles/tile-counter",getprop(lw~"tiles/tile-counter")-1);return();} # mark tile as active append(weather_tile_management.active_tile_list,1); # start tile management loop if needed if (getprop(lw~"tmp/tile-management") != "single tile") { if (getprop(lw~"tile-loop-flag") == 0) { setprop(lw~"tiles/tile[4]/code",getprop(lw~"tiles/code")); setprop(lw~"tile-loop-flag",1); weather_tile_management.tile_management_loop();} } # start the interpolation loop if (getprop(lw~"interpolation-loop-flag") == 0) {setprop(lw~"interpolation-loop-flag",1); local_weather.interpolation_loop();} # start the effect volume loop if (getprop(lw~"effect-loop-flag") == 0) {setprop(lw~"effect-loop-flag",1); local_weather.effect_volume_loop(0,0);} # start weather dynamics loops if needed if (dynamics_flag ==1) { if (getprop(lw~"timing-loop-flag") == 0) {setprop(lw~"timing-loop-flag",1); weather_dynamics.timing_loop();} if (getprop(lw~"dynamics-loop-flag") == 0) { setprop(lw~"dynamics-loop-flag",1); weather_dynamics.quadtree_loop(); weather_dynamics.weather_dynamics_loop(0,0); } if ((getprop(lw~"convective-loop-flag") == 0) and (getprop(lw~"config/dynamical-convection-flag") ==1)) { setprop(lw~"convective-loop-flag",1); weather_dynamics.convective_loop(); } } # and start the buffer loop and housekeeping loop if needed if (getprop(lw~"config/buffer-flag") ==1) { if (getprop(lw~"buffer-loop-flag") == 0) { setprop(lw~"buffer-loop-flag",1); weather_tile_management.buffer_loop(0); setprop(lw~"housekeeping-loop-flag",1); weather_tile_management.housekeeping_loop(0); } } # weather_tile_management.watchdog_loop(); } ################################################# # Anything that needs to run at startup goes here ################################################# var startup = func { print("Loading local weather routines..."); # get local Cartesian geometry var lat = getprop("position/latitude-deg"); var lon = getprop("position/longitude-deg"); calc_geo(lat); # copy weather properties at startup to local weather setprop(lw~"interpolation/visibility-m",getprop(ec~"boundary/entry[0]/visibility-m")); setprop(lw~"interpolation/pressure-sea-level-inhg",getprop(ec~"boundary/entry[0]/pressure-sea-level-inhg")); setprop(lw~"interpolation/temperature-degc",getprop(ec~"boundary/entry[0]/temperature-degc")); setprop(lw~"interpolation/wind-from-heading-deg",getprop(ec~"boundary/entry[0]/wind-from-heading-deg")); setprop(lw~"interpolation/wind-speed-kt",getprop(ec~"boundary/entry[0]/wind-speed-kt")); setprop(lw~"interpolation/turbulence",getprop(ec~"boundary/entry[0]/turbulence/magnitude-norm")); setprop(lw~"interpolation/rain-norm",0.0); setprop(lw~"interpolation/snow-norm",0.0); setprop(lw~"interpolation/thermal-lift",0.0); # before interpolation starts, these are also initially current setprop(lw~"current/visibility-m",getprop(lwi~"visibility-m")); setprop(lw~"current/pressure-sea-level-inhg",getprop(lw~"interpolation/pressure-sea-level-inhg")); setprop(lw~"current/temperature-degc",getprop(lw~"interpolation/temperature-degc")); setprop(lw~"current/wind-from-heading-deg",getprop(lw~"interpolation/wind-from-heading-deg")); setprop(lw~"current/wind-speed-kt",getprop(lw~"interpolation/wind-speed-kt")); setprop(lw~"current/rain-norm",getprop(lw~"interpolation/rain-norm")); setprop(lw~"current/snow-norm",getprop(lw~"interpolation/snow-norm")); setprop(lw~"current/thermal-lift",getprop(lw~"interpolation/thermal-lift")); setprop(lw~"current/turbulence",getprop(lwi~"turbulence")); # create default properties for METAR system, should be overwritten by real-weather-fetch setprop(lw~"METAR/latitude-deg",lat); setprop(lw~"METAR/longitude-deg",lon); setprop(lw~"METAR/altitude-ft",0.0); setprop(lw~"METAR/wind-direction-deg",0.0); setprop(lw~"METAR/wind-strength-kt",10.0); setprop(lw~"METAR/visibility-m",17000.0); setprop(lw~"METAR/rain-norm",0.0); setprop(lw~"METAR/snow-norm",0.0); setprop(lw~"METAR/temperature-degc",10.0); setprop(lw~"METAR/dewpoint-degc",7.0); setprop(lw~"METAR/pressure-inhg",29.92); setprop(lw~"METAR/thunderstorm-flag",0); setprop(lw~"METAR/layer[0]/cover-oct",4); setprop(lw~"METAR/layer[0]/alt-agl-ft", 3000.0); setprop(lw~"METAR/layer[1]/cover-oct",0); setprop(lw~"METAR/layer[1]/alt-agl-ft", 20000.0); setprop(lw~"METAR/layer[2]/cover-oct",0); setprop(lw~"METAR/layer[2]/alt-agl-ft", 20000.0); setprop(lw~"METAR/layer[3]/cover-oct",0); setprop(lw~"METAR/layer[3]/alt-agl-ft", 20000.0); setprop(lw~"METAR/available-flag",1); # set listeners setlistener(lw~"tmp/thread-status", func {var s = size(clouds_path); compat_layer.create_cloud_array(s, clouds_path, clouds_lat, clouds_lon, clouds_alt, clouds_orientation); }); setlistener(lw~"tmp/convective-status", func {var s = size(clouds_path); compat_layer.create_cloud_array(s, clouds_path, clouds_lat, clouds_lon, clouds_alt, clouds_orientation); }); setlistener(lw~"tmp/effect-thread-status", func {var s = size(effects_geo); effect_placement_loop(s); }); setlistener(lw~"tmp/presampling-status", func {manage_presampling(); }); setlistener(lw~"config/wind-model", func {set_wind_model_flag();}); setlistener(lw~"config/thermal-properties", func {set_texture_mix();}); setlistener(lw~"config/clouds-in-dynamics-loop", func {weather_dynamics.max_clouds_in_loop = int(getprop(lw~"config/clouds-in-dynamics-loop"));}); setlistener(lw~"config/clouds-visible-range-m", func {weather_tile_management.cloud_view_distance = getprop(lw~"config/clouds-visible-range-m");}); setlistener(lw~"config/distance-to-load-tile-m", func {setprop(lw~"config/distance-to-remove-tile-m",getprop(lw~"config/distance-to-load-tile-m") + 500.0);}); setlistener(lw~"config/fps-control-flag", func {fps_control_flag = getprop(lw~"config/fps-control-flag");}); setlistener(lw~"config/target-framerate", func {target_framerate = getprop(lw~"config/target-framerate");}); setlistener(lw~"config/small-scale-persistence", func {weather_tiles.small_scale_persistence = getprop(lw~"config/small-scale-persistence");}); } ##################################################### # Standard test call (for development and debug only) ##################################################### var test = func { var lat = getprop("position/latitude-deg"); var lon = getprop("position/longitude-deg"); #var pos = geo.aircraft_position(); debug.dump(geodinfo(lat, lon)); # geo.put_model("Models/Astro/Earth.ac",lat, lon); #setprop("/environment/terrain/area[0]/input/latitude-deg", lat ); #setprop("/environment/terrain/area[0]/input/longitude-deg", lon ); #setprop("/environment/terrain/area[0]/output/valid", 0 ); } ################################################################# # object classes ################################################################# var weatherStation = { new: func (lat, lon, alt, vis, T, D, p) { var s = { parents: [weatherStation] }; s.lat = lat; s.lon = lon; s.alt = alt; s.vis = vis; s.T = T; s.D = D; s.p = p; s.scattering = 0.8; return s; }, move: func { var windfield = weather_dynamics.get_windfield(me.index); var dt = weather_dynamics.time_lw - me.timestamp; me.lat = me.lat + windfield[1] * dt * local_weather.m_to_lat; me.lon = me.lon + windfield[0] * dt * local_weather.m_to_lon; me.timestamp = weather_dynamics.time_lw; }, }; var atmosphereIpoint = { new: func (lat, lon, vis_aloft, vis_alt1, vis_ovcst, ovcst, ovcst_alt_low, ovcst_alt_high, scatt, scatt_alt_low, scatt_alt_high){ var a = { parents: [atmosphereIpoint] }; a.lat = lat; a.lon = lon; a.vis_aloft = vis_aloft; a.vis_alt1 = vis_alt1; a.vis_ovcst = vis_ovcst; a.ovcst = ovcst; a.ovcst_alt_low = ovcst_alt_low; a.ovcst_alt_high = ovcst_alt_high; a.scatt = scatt; a.scatt_alt_low = scatt_alt_low; a.scatt_alt_high = scatt_alt_high; return a; }, move: func { var windfield = weather_dynamics.get_windfield(me.index); var dt = weather_dynamics.time_lw - me.timestamp; me.lat = me.lat + windfield[1] * dt * local_weather.m_to_lat; me.lon = me.lon + windfield[0] * dt * local_weather.m_to_lon; me.timestamp = weather_dynamics.time_lw; }, }; var windIpoint = { new: func (lat, lon, d0, v0, d1, v1, d2, v2, d3, v3, d4, v4, d5, v5, d6, v6, d7, v7, d8, v8) { var w = { parents: [windIpoint] }; w.lat = lat; w.lon = lon; altvec = []; var wv = windVec.new(d0, v0); append(altvec,wv); wv = windVec.new(d1, v1); append(altvec, wv); wv = windVec.new(d2, v2); append(altvec, wv); wv = windVec.new(d3, v3); append(altvec, wv); wv = windVec.new(d4, v4); append(altvec, wv); wv = windVec.new(d5, v5); append(altvec, wv); wv = windVec.new(d6, v6); append(altvec, wv); wv = windVec.new(d7, v7); append(altvec, wv); wv = windVec.new(d8, v8); append(altvec, wv); w.alt = altvec; w.weight = 0.02; return w; }, }; var windVec = { new: func (d, v) { var wv = { parents: [windVec] }; wv.d = d; wv.v = v; return wv; }, }; var effectVolume = { new: func (geometry, lat, lon, r1, r2, phi, alt_low, alt_high, vis, rain, snow, turb, lift, lift_flag, sat) { var e = { parents: [effectVolume] }; e.geometry = geometry; e.lat = lat; e.lon = lon; e.r1 = r1; e.r2 = r2; e.phi = phi; e.alt_low = alt_low; e.alt_high = alt_high; e.vis = vis; e.rain = rain; e.snow = snow; e.turb = turb; e.lift = lift; e.lift_flag = lift_flag; e.sat = sat; return e; }, move: func { var windfield = weather_dynamics.get_windfield(me.index); var dt = weather_dynamics.time_lw - me.timestamp; me.lat = me.lat + windfield[1] * dt * local_weather.m_to_lat; me.lon = me.lon + windfield[0] * dt * local_weather.m_to_lon; me.timestamp = weather_dynamics.time_lw; }, correct_altitude: func { var convective_alt = weather_dynamics.tile_convective_altitude[me.index-1] + alt_20_array[me.index-1]; var elevation = compat_layer.get_elevation(me.lat, me.lon); me.alt_high = local_weather.get_convective_altitude(convective_alt, elevation, me.index) *1.15; me.height = me.alt_high * 0.87; }, correct_altitude_and_age: func { var convective_alt = weather_dynamics.tile_convective_altitude[me.index-1] + local_weather.alt_20_array[me.index-1]; var elevation = -1.0; var p_cover = 0.2; var info = geodinfo(me.lat, me.lon); if (info != nil) { elevation = info[0] * local_weather.m_to_ft; if (info[1] != nil) { var landcover = info[1].names[0]; if (contains(landcover_map,landcover)) {p_cover = landcover_map[landcover];} else {p_cover = 0.2;} } } me.alt_high = get_convective_altitude(convective_alt, elevation, me.index) * 1.15; me.height = me.alt_high * 0.87; var current_lifetime = math.sqrt(p_cover)/math.sqrt(0.35) * weather_dynamics.cloud_convective_lifetime_s; var fractional_increase = (weather_dynamics.time_lw - me.evolution_timestamp)/current_lifetime; me.flt = me.flt + fractional_increase; me.evolution_timestamp = weather_dynamics.time_lw; }, get_distance: func { var lat = getprop("position/latitude-deg"); var lon = getprop("position/longitude-deg"); return math.sqrt(calc_d_sq(lat, lon, me.lat, me.lon)); }, }; var thermalLift = { new: func (lat, lon, radius, height, cn, sh, max_lift, f_lift_radius) { var l = { parents: [thermalLift] }; l.lat = lat; l.lon = lon; l.radius = radius; l.height = height; l.cn = cn; l.sh = sh; l.max_lift = max_lift; l.f_lift_radius = f_lift_radius; return l; }, move: func { var windfield = weather_dynamics.get_windfield(me.index); var dt = weather_dynamics.time_lw - me.timestamp; me.lat = me.lat + windfield[1] * dt * local_weather.m_to_lat; me.lon = me.lon + windfield[0] * dt * local_weather.m_to_lon; me.timestamp = weather_dynamics.time_lw; }, correct_altitude: func { var convective_alt = weather_dynamics.tile_convective_altitude[me.index-1] + alt_20_array[me.index-1]; var elevation = compat_layer.get_elevation(me.lat, me.lon); me.height = local_weather.get_convective_altitude(convective_alt, elevation, me.index); }, correct_altitude_and_age: func { var convective_alt = weather_dynamics.tile_convective_altitude[me.index-1] + local_weather.alt_20_array[me.index-1]; var elevation = -1.0; var p_cover = 0.2; var info = geodinfo(me.lat, me.lon); if (info != nil) { elevation = info[0] * local_weather.m_to_ft; if (info[1] != nil) { var landcover = info[1].names[0]; if (contains(landcover_map,landcover)) {p_cover = landcover_map[landcover];} else {p_cover = 0.2;} } } me.height = get_convective_altitude(convective_alt, elevation, me.index); var current_lifetime = math.sqrt(p_cover)/math.sqrt(0.35) * weather_dynamics.cloud_convective_lifetime_s; var fractional_increase = (weather_dynamics.time_lw - me.evolution_timestamp)/current_lifetime; me.flt = me.flt + fractional_increase; me.evolution_timestamp = weather_dynamics.time_lw; }, }; var waveLift = { new: func (lat, lon, x, y, phi, height, max_lift) { var w = { parents: [waveLift] }; w.lat = lat; w.lon = lon; w.x = x; w.y = y; w.phi = phi; w.height = height; w.max_lift = max_lift; w.phi = getprop(lw~"tmp/tile-orientation-deg"); return w; }, }; ################################################################# # global variable, property creation and the startup listener ################################################################# var rad_E = 6378138.12; # earth radius var lat_to_m = 110952.0; # latitude degrees to meters var m_to_lat = 9.01290648208234e-06; # meters to latitude degrees var ft_to_m = 0.30480; var m_to_ft = 1.0/ft_to_m; var lon_to_m = 0.0; # needs to be calculated dynamically var m_to_lon = 0.0; # we do this on startup # some common abbreviations var lw = "/local-weather/"; var lwi = "/local-weather/interpolation/"; var ec = "/environment/config/"; # a hash map of the strength for convection associated with terrain types var landcover_map = {BuiltUpCover: 0.35, Town: 0.35, Freeway:0.35, BarrenCover:0.3, HerbTundraCover: 0.25, GrassCover: 0.2, CropGrassCover: 0.2, EvergreenBroadCover: 0.2, EvergreenNeedleCover: 0.2, Sand: 0.25, Grass: 0.2, Ocean: 0.01, Marsh: 0.05, Lake: 0.01, ShrubCover: 0.15, Landmass: 0.2, CropWoodCover: 0.15, MixedForestCover: 0.1, DryCropPastureCover: 0.25, MixedCropPastureCover: 0.2, IrrCropPastureCover: 0.15, DeciduousBroadCover: 0.1, DeciduousNeedleCover: 0.1, Bog: 0.05, pa_taxiway : 0.35, pa_tiedown: 0.35, pc_taxiway: 0.35, pc_tiedown: 0.35, Glacier: 0.01, DryLake: 0.3, IntermittentStream: 0.2, DryCrop: 0.2, Lava: 0.3}; # a hash map of average vertical cloud model sizes var cloud_vertical_size_map = {Altocumulus: 700.0, Cumulus: 600.0, Nimbus: 1000.0, Stratus: 800.0, Stratus_structured: 600.0, Stratus_thin: 400.0, Cirrocumulus: 200.0}; # the array of aloft wind interpolation altitudes var wind_altitude_array = [0.0, 5000.0, 10000.0, 18000.0, 24000.0, 30000.0, 34000.0, 39000.0, 45000.0]; # storage arrays for cloud generation var clouds_path = []; var clouds_lat = []; var clouds_lon = []; var clouds_alt = []; var clouds_orientation = []; # additional info needed for dynamical clouds: the base altitude around which cloudlets are distributed # and the fractional lifetime var clouds_mean_alt = []; var clouds_flt = []; var clouds_evolution_timestamp = []; # storage arrays for terrain presampling and results by tile var terrain_n = []; var alt_50_array = []; var alt_20_array = []; # array of currently existing effect volumes var effectVolumeArray = []; var n_effectVolumeArray = 0; # the thermal and the wave hash var thermal = {}; var wave = {}; # arrays of currently existing weather stations, wind interpolation and atmospheric condition points var weatherStationArray = []; var windIpointArray = []; var atmosphereIpointArray = []; # a flag for the wind model (so we don't have to do string comparisons all the time) # 1: constant 2: constant in tile 3: aloft interpolated 4: airmass interpolated var wind_model_flag = 1; # globals governing properties of the Cumulus system var convective_texture_mix = 0.0; var convective_size_bias = 0.0; var cumulus_efficiency_factor = 1.0; var cloud_mean_altitude = 0.0; # globals keeping track of the lifetime when building a Cumulus from individual cloudlets var cloud_fractional_lifetime = 0.0; var cloud_evolution_timestamp = 0.0; # globals propagating gust information inside the interpolation loop var windspeed_multiplier = 1.0; var winddir_change = 0.0; # global flags mirroring property tree menu settings var generate_thermal_lift_flag = 0; var thread_flag = 1; var dynamics_flag = 1; var presampling_flag = 1; var detailed_clouds_flag = 1; var dynamical_convection_flag = 1; var debug_output_flag = 1; var metar_flag = 0; var local_weather_running_flag = 0; var local_weather_startup_flag = 0; var fps_control_flag = 0; var detailed_terrain_interaction_flag = 0; # globals for framerate controlled cloud management var fps_average = 0.0; var fps_samples = 0; var fps_sum = 0.0; var target_framerate = 25.0; # set all sorts of default properties for the menu setprop(lw~"tmp/scloud-lat",getprop("position/latitude-deg")); setprop(lw~"tmp/scloud-lon",getprop("position/longitude-deg")); setprop(lw~"tmp/tile-alt-median-ft",0.0); setprop(lw~"tmp/tile-alt-min-ft",0.0); setprop(lw~"tmp/last-reading-pos-del",0); setprop(lw~"tmp/last-reading-pos-mod",0); setprop(lw~"tmp/thread-status", "idle"); setprop(lw~"tmp/convective-status", "idle"); setprop(lw~"tmp/presampling-status", "idle"); setprop(lw~"tmp/buffer-status", "idle"); setprop(lw~"tmp/buffer-tile-index", 0); setprop(lw~"tmp/ipoint-latitude-deg",getprop("position/latitude-deg")); setprop(lw~"tmp/ipoint-longitude-deg",getprop("position/longitude-deg")); # set config values # setprop(lw~"config/distance-to-load-tile-m",39000.0); # setprop(lw~"config/distance-to-remove-tile-m",39500.0); # setprop(lw~"config/detailed-clouds-flag",1); # setprop(lw~"config/dynamics-flag",0); # setprop(lw~"config/thermal-properties",1.0); # setprop(lw~"config/wind-model","constant"); # setprop(lw~"config/buffer-flag",1); # setprop(lw~"config/asymmetric-reduction",0.7); # setprop(lw~"config/clouds-visible-range-m",30000.0); # setprop(lw~"config/asymmetric-buffering-flag",0); # setprop(lw~"config/asymmetric-buffering-reduction",0.3); # setprop(lw~"config/asymmetric-buffering-angle-deg",90.0); # setprop(lw~"config/clouds-in-dynamics-loop",250); # setprop(lw~"config/debug-output-flag",0); # setprop(lw~"config/generate-thermal-lift-flag", 0); # setprop(lw~"config/dynamical-convection-flag", 0); # setprop(lw~"config/thread-flag", 1); # setprop(lw~"config/presampling-flag", 1); # set the default loop flags to loops inactive setprop(lw~"effect-loop-flag",0); setprop(lw~"interpolation-loop-flag",0); setprop(lw~"tile-loop-flag",0); setprop(lw~"lift-loop-flag",0); setprop(lw~"wave-loop-flag",0); setprop(lw~"buffer-loop-flag",0); setprop(lw~"housekeeping-loop-flag",0); setprop(lw~"convective-loop-flag",0); # create other management properties #setprop(lw~"clouds/cloud-number",0); setprop(lw~"clouds/placement-index",0); setprop(lw~"clouds/model-placement-index",0); setprop(lw~"effect-volumes/effect-placement-index",0); # create properties for effect volume management setprop(lw~"effect-volumes/number",0); setprop(lw~"effect-volumes/number-active-vis",0); setprop(lw~"effect-volumes/number-active-rain",0); setprop(lw~"effect-volumes/number-active-snow",0); setprop(lw~"effect-volumes/number-active-turb",0); setprop(lw~"effect-volumes/number-active-lift",0); setprop(lw~"effect-volumes/number-active-sat",0); # create properties for tile management setprop(lw~"tiles/tile-counter",0); # create properties for wind setprop(lwi~"ipoint-number",0); var updateMenu = func { var isEnabled = getprop("/nasal/local_weather/enabled"); gui.menuEnable("local_weather", isEnabled); gui.menuEnable("local_weather_tiles", isEnabled); } _setlistener("/nasal/local_weather/enabled", updateMenu);