1######################################################## 2# routines to set up, transform and manage weather tiles 3# Thorsten Renk, March 2011 4######################################################## 5 6# function purpose 7# 8# tile_management_loop to decide if a tile is created, removed or considered current 9# generate_tile to decide on orientation and type and set up all information for tile creation 10# remove_tile to delete a tile by index 11# change_active_tile to change the tile the aircraft is currently in and to generate neighbour info 12# copy_entry to copy tile information from one node to another 13# create_neighbour to set up information for a new neighbouring tile 14# create_neighbours to initialize the 8 neighbours of the initial tile 15# buffer_loop to manage the buffering of faraway clouds in an array 16# housekeeping_loop to shift clouds from the scenery into the buffer 17# remove_impostors to delete a ring of impostors to mimick distant clouds 18# create_impostors to create a ring of impostors to mimick distant clouds 19# shadow_management_loop to manage cloud shadow information 20# thunderstorm_management_loop to manage information on thunderstorm position 21# lightning_strike to get the timing for a lightning strike to the property tree 22# watchdog loop (debug helping structure) 23# calc_geo to get local Cartesian geometry for latitude conversion 24# get_lat to get latitude from Cartesian coordinates 25# get_lon to get longitude from Cartesian coordinates 26# relangle to compute the relative angle between two directions, normalized to [0:180] 27# norm_relangle to compute the relative angle between two directions, normalized to [0:360] 28# delete_from_vector to delete an element 'n' from a vector 29 30# object purpose 31# 32# cloud to provide the data hash for the new cloud rendering system 33# cloudBuffer to store a cloud in a Nasal buffer, to provide methods to move it 34# cloudScenery to store info for clouds in scenery, to provide methods to move and evolve them 35# cloudImpostor to provide the hash data for an impostor cloud sheet 36 37 38################################### 39# tile management loop 40################################### 41 42var tile_management_loop = func { 43 44 45if (local_weather.local_weather_running_flag == 0) {return;} 46 47var tNode = props.globals.getNode(lw~"tiles", 1).getChildren("tile"); 48var viewpos = geo.aircraft_position(); # using viewpos here triggers massive tile ops for tower view... 49var code = getprop(lw~"tiles/tile[4]/code"); 50var i = 0; 51var d_min = 100000.0; 52var i_min = 0; 53var current_visibility = local_weather.interpolated_conditions.visibility_m; 54var current_heading = getprop("orientation/heading-deg"); 55var loading_flag = getprop(lw~"tmp/asymmetric-tile-loading-flag"); 56var this_frame_action_flag = 0; # use this flag to avoid overlapping tile operations 57 58setsize(active_tile_list,0); 59#append(active_tile_list,0); # tile zero formally containing static objects is always active 60 61var distance_to_load = current_visibility + 10000.0; 62 63if (distance_to_load > 65000.0) {distance_to_load = 65000.0;} 64if (distance_to_load < 29000.0) {distance_to_load = 29000.0;} 65 66 67 68 69var distance_to_remove = distance_to_load + 20000.0; 70if (distance_to_remove > 65500.0) {distance_to_remove = 65500.0;} 71 72 73# check here if we have a new weather station if METAR is running 74 75if ((local_weather.metar_flag == 1) and (getprop(lw~"METAR/station-id") != getprop("/environment/metar/station-id"))) 76 { 77 weather_tiles.set_METAR_weather_station(); 78 } 79 80# compute the averaged framerate and see if cloud visibility needs to be adjusted 81 82if (local_weather.fps_control_flag == 1) 83 { 84 local_weather.fps_average = local_weather.fps_sum/local_weather.fps_samples; 85 86 # print("Average framerate: ", local_weather.fps_average); 87 88 local_weather.fps_sum = 0.0; 89 local_weather.fps_samples = 0; 90 91 if (local_weather.fps_average > 1.1 * local_weather.target_framerate) 92 { 93 var target_cloud_view_distance = cloud_view_distance * 1.1; 94 if (target_cloud_view_distance > 45000.0) 95 {target_cloud_view_distance = 45000.0;} 96 setprop(lw~"config/clouds-visible-range-m", target_cloud_view_distance); 97 } 98 if (local_weather.fps_average < 0.9 * local_weather.target_framerate) 99 { 100 var target_cloud_view_distance = cloud_view_distance * 0.9; 101 if (target_cloud_view_distance < 15000.0) 102 {target_cloud_view_distance = 15000.0;} 103 setprop(lw~"config/clouds-visible-range-m", target_cloud_view_distance); 104 } 105 106 } 107 108 109 110 111foreach (var t; tNode) { 112 113 var tpos = geo.Coord.new(); 114 tpos.set_latlon(t.getNode("latitude-deg").getValue(),t.getNode("longitude-deg").getValue(),0.0); 115 var d = viewpos.distance_to(tpos); 116 if (d < d_min) {d_min = d; i_min = i;} 117 var flag = t.getNode("generated-flag").getValue(); 118 119 if ((flag ==2) or (flag ==1)) {append(active_tile_list,t.getNode("tile-index").getValue());} 120 121 var dir = viewpos.course_to(tpos); 122 var d_load = distance_to_load; 123 var d_remove = distance_to_remove; 124 if (loading_flag == 1) 125 { 126 var angle = abs(dir-current_heading); 127 #if (i==7) {print(angle);} 128 if ((angle > 135.0) and (angle < 225.0)) 129 { 130 d_load = 0.7 * d_load; 131 d_remove = 0.7 * d_remove; 132 } 133 } 134 135 # the tile needs to be generated, unless it already has been 136 # and if no other tile has been generated in this loop cycle 137 # and the thread and convective system are idle 138 # (we want to avoid overlapping tile generation) 139 140 if ((d < d_load) and (flag==0) and (this_frame_action_flag == 0) and (getprop(lw~"tmp/thread-status") == "idle") and (getprop(lw~"tmp/convective-status") == "idle") and (getprop(lw~"tmp/presampling-status") == "idle")) 141 { 142 this_frame_action_flag = 1; 143 setprop(lw~"tiles/tile-counter",getprop(lw~"tiles/tile-counter")+1); 144 if (local_weather.debug_output_flag == 1) 145 {print("Building tile unique index ",getprop(lw~"tiles/tile-counter"), " in direction ",i);} 146 append(active_tile_list,getprop(lw~"tiles/tile-counter")); 147 148 if (local_weather.dynamics_flag == 1) 149 { 150 var quadtree = []; 151 weather_dynamics.generate_quadtree_structure(0, quadtree); 152 append(weather_dynamics.cloudQuadtrees,quadtree); 153 } 154 155 t.getNode("generated-flag").setValue(1); 156 t.getNode("timestamp-sec").setValue(weather_dynamics.time_lw); 157 t.getNode("tile-index",1).setValue(getprop(lw~"tiles/tile-counter")); 158 generate_tile(code, tpos.lat(), tpos.lon(),i); 159 160 } 161 162 if ((d > d_remove) and (flag == 2) and (this_frame_action_flag == 0)) # the tile needs to be deleted if it exists 163 { 164 if (local_weather.debug_output_flag == 1) 165 {print("Removing tile, unique index ", t.getNode("tile-index").getValue()," direction ",i);} 166 remove_tile(t.getNode("tile-index").getValue()); 167 t.getNode("generated-flag").setValue(0); 168 this_frame_action_flag = 1; 169 } 170 i = i + 1; 171 } # end foreach 172 173 #print("Minimum distance to: ",i_min); 174 175 var presampling_status = getprop(lw~"tmp/presampling-status"); 176 var convective_status = getprop(lw~"tmp/convective-status"); 177 var thread_status = getprop(lw~"tmp/thread-status"); 178 179 if ((presampling_status == "idle") and (convective_status == "idle") and (thread_status == "idle")) 180 { 181 var system_status = "idle"; 182 } 183 else 184 {system_status = "computing";} 185 186 # and (this_frame_action_flag == 0) and (presampling_status == "idle") and (convective_status=="idle")) 187 188 # check if we've entered a different tile and if no operation is in progress 189 190 # var gen_flag = tNode[i_min].getNode("generated-flag").getValue(); 191 if ((i_min != 4) and (system_status == "idle")) 192 { 193 194 var gen_flag = tNode[i_min].getNode("generated-flag").getValue(); 195 if (gen_flag != 2) { 196 logprint(DEV_WARN, "Tile direction ",i_min, " not generated!"); 197 logprint(DEV_WARN, "Flag: ",gen_flag); 198 } 199 200 if (local_weather.debug_output_flag == 1) 201 {logprint(LOG_DEBUG, "Changing active tile to direction ", i_min);} 202 change_active_tile(i_min); 203 204 } 205 206 207 208if (getprop(lw~"tile-loop-flag") ==1) {settimer(tile_management_loop, 4.0);} 209 210} 211 212 213################################### 214# tile generation call 215################################### 216 217var generate_tile = func (code, lat, lon, dir_index) { 218 219 220# the code should never be NIL, but this appears to happen under certain conditions 221# so just to be on the safe side make sure it is set to current tile code if 222# it actually is NIL 223 224if (code == "") 225 { 226 print("No tile code - falling back on default!"); 227 code = getprop(lw~"tiles/code"); 228 } 229 230setprop(lw~"tiles/tmp/latitude-deg", lat); 231setprop(lw~"tiles/tmp/longitude-deg",lon); 232setprop(lw~"tiles/tmp/code",code); 233setprop(lw~"tiles/tmp/dir-index",dir_index); 234 235 236# do windspeed and orientation before presampling check, but test not to do it again 237 238if (((local_weather.presampling_flag == 1) and (getprop(lw~"tmp/presampling-status") == "idle")) or (local_weather.presampling_flag == 0)) 239 { 240 241 var alpha = getprop(lw~"tmp/tile-orientation-deg"); 242 243 244 if ((local_weather.wind_model_flag == 2) or (local_weather.wind_model_flag ==4)) 245 { 246 247 if (local_weather.metar_flag == 0) 248 { 249 alpha = alpha + 2.0 * (rand()-0.5) * 10.0; 250 # account for the systematic spin of weather systems around a low pressure 251 # core dependent on hemisphere 252 if (lat >0.0) {alpha = alpha -3.0;} 253 else {alpha = alpha +3.0;} 254 } 255 else 256 { 257 258 var step = 20.0; 259 260 var alpha_test = getprop("/environment/metar/base-wind-dir-deg"); 261 262 263 if (local_weather.debug_output_flag == 1) 264 {print("alpha: ", alpha, " alpha_test: ", alpha_test, " relangle: ", relangle(alpha, alpha_test));} 265 266 267 var coordinate_rotation_angle = norm_relangle(alpha, alpha_test); 268 269 270 if (coordinate_rotation_angle < 45.0) 271 { 272 var system_rotation_angle = 0; 273 var displacement_angle = coordinate_rotation_angle; 274 } 275 else if (coordinate_rotation_angle < 135.0) 276 { 277 var system_rotation_angle = 90.0; 278 var displacement_angle = coordinate_rotation_angle - 90.0; 279 } 280 else if (coordinate_rotation_angle < 225.0) 281 { 282 var system_rotation_angle = 180.0; 283 var displacement_angle = coordinate_rotation_angle - 180.0; 284 } 285 else if (coordinate_rotation_angle < 315.0) 286 { 287 var system_rotation_angle = 270.0; 288 var displacement_angle = coordinate_rotation_angle - 270.0; 289 } 290 else 291 { 292 var system_rotation_angle = 0; 293 var displacement_angle = coordinate_rotation_angle - 360.0; 294 } 295 296 297 if (displacement_angle < -step) 298 { 299 print("Coordinate rotation by more than ",step," deg... compensating"); 300 displacement_angle = -step; 301 } 302 else if (displacement_angle > step) 303 { 304 print("Coordinate rotation by more than ",step," deg... compensating"); 305 displacement_angle = step; 306 } 307 308 alpha = alpha + system_rotation_angle + displacement_angle; 309 310 311 312 #if (relangle(alpha, alpha_test) > step) 313 # { 314 # print("Coordinate rotation by more than ",step," deg... compensating"); 315 # if (relangle(alpha + step, alpha_test) < relangle(alpha-step, alpha_test)) 316 # { 317 # alpha = alpha + step; 318 # } 319 # else 320 # { 321 # alpha = alpha - step; 322 # } 323 # } 324 #else 325 # { 326 # alpha = alpha_test; 327 # } 328 } 329 330 331 332 333 setprop(lw~"tmp/tile-orientation-deg",alpha); 334 335 # compute the new windspeed 336 337 var windspeed = 0; 338 if (local_weather.metar_flag == 0) 339 { 340 windspeed = getprop(lw~"tmp/windspeed-kt"); 341 windspeed = windspeed + 2.0 * (rand()-0.5) * 2.0; 342 if (windspeed < 0) {windspeed = rand();} 343 } 344 else 345 { 346 var boundary_correction = 1.0/local_weather.get_slowdown_fraction(); 347 windspeed = boundary_correction * getprop("/environment/metar/base-wind-speed-kt"); 348 } 349 350 setprop(lw~"tmp/windspeed-kt",windspeed); 351 352 # store the tile orientation and wind strength in an array for fast processing 353 354 append(weather_dynamics.tile_wind_direction, alpha); 355 append(weather_dynamics.tile_wind_speed, windspeed); 356 357 } 358 else if (local_weather.wind_model_flag ==5) # alpha and windspeed are calculated 359 { 360 var res = local_weather.wind_interpolation(lat,lon,0.0); 361 362 var step = 20.0; 363 var alpha_test = res[0]; 364 365 366 if (local_weather.debug_output_flag == 1) 367 {print("alpha: ", alpha, " alpha_test: ", alpha_test, " relangle: ", relangle(alpha, alpha_test));} 368 369 370 var coordinate_rotation_angle = norm_relangle(alpha, alpha_test); 371 372 #print("Norm_relangle : ", norm_relangle(alpha, alpha_test)); 373 374 375 if (coordinate_rotation_angle < 45.0) 376 { 377 var system_rotation_angle = 0; 378 var displacement_angle = coordinate_rotation_angle; 379 } 380 else if (coordinate_rotation_angle < 135.0) 381 { 382 var system_rotation_angle = 90.0; 383 var displacement_angle = coordinate_rotation_angle - 90.0; 384 } 385 else if (coordinate_rotation_angle < 225.0) 386 { 387 var system_rotation_angle = 180.0; 388 var displacement_angle = coordinate_rotation_angle - 180.0; 389 } 390 else if (coordinate_rotation_angle < 315.0) 391 { 392 var system_rotation_angle = 270.0; 393 var displacement_angle = coordinate_rotation_angle - 270.0; 394 } 395 else 396 { 397 var system_rotation_angle = 0; 398 var displacement_angle = coordinate_rotation_angle - 360.0; 399 } 400 401 #print("Displacement angle: ", displacement_angle); 402 403 if (displacement_angle < -step) 404 { 405 print("Coordinate rotation by more than ",step," deg... compensating"); 406 displacement_angle = -step; 407 } 408 else if (displacement_angle > step) 409 { 410 print("Coordinate rotation by more than ",step," deg... compensating"); 411 displacement_angle = step; 412 } 413 414 #print("Normalized displacement angle: ", displacement_angle); 415 416 alpha = alpha + system_rotation_angle + displacement_angle; 417 418 #print("alpha_out: ", alpha); 419 420 #if (relangle(alpha, alpha_test) > step) 421 # { 422 # print("Coordinate rotation by more than ",step," deg... compensating"); 423 # if (relangle(alpha + step, alpha_test) < relangle(alpha-step, alpha_test)) 424 # { 425 # alpha = alpha + step; 426 # } 427 # else 428 # { 429 # alpha = alpha - step; 430 # } 431 # } 432 #else 433 # { 434 # alpha = alpha_test; 435 # } 436 437 #alpha = alpha_test; 438 439 440 441 setprop(lw~"tmp/tile-orientation-deg",alpha); 442 var windspeed = res[1]; 443 setprop(lw~"tmp/windspeed-kt",windspeed); 444 445 append(weather_dynamics.tile_wind_direction,res[0]); 446 append(weather_dynamics.tile_wind_speed,res[1]); 447 448 } 449 450 451 props.globals.getNode(lw~"tiles").getChild("tile",dir_index).getNode("orientation-deg").setValue(alpha); 452 } 453 454 455 456# now see if we need to presample the terrain 457 458if ((local_weather.presampling_flag == 1) and (getprop(lw~"tmp/presampling-status") == "idle") and (compat_layer.features.terrain_presampling_active == 0)) 459 { 460 local_weather.terrain_presampling_start(lat, lon, 1000, 40000, getprop(lw~"tmp/tile-orientation-deg")); 461 return; 462 } 463else if (compat_layer.features.terrain_presampling_active == 1)# we have hard-coded values available and use those 464 {local_weather.terrain_presampling_analysis();} 465 466 467if (local_weather.debug_output_flag == 1) 468 {print("Current tile type: ", code);} 469 470if (getprop(lw~"tmp/tile-management") == "repeat tile") 471 { 472 if (code == "altocumulus_sky"){weather_tiles.set_altocumulus_tile();} 473 else if (code == "broken_layers") {weather_tiles.set_broken_layers_tile();} 474 else if (code == "stratus") {weather_tiles.set_overcast_stratus_tile();} 475 else if (code == "cumulus_sky") {weather_tiles.set_fair_weather_tile();} 476 else if (code == "gliders_sky") {weather_tiles.set_gliders_sky_tile();} 477 else if (code == "blue_thermals") {weather_tiles.set_blue_thermals_tile();} 478 else if (code == "summer_rain") {weather_tiles.set_summer_rain_tile();} 479 else if (code == "high_pressure_core") {weather_tiles.set_high_pressure_core_tile();} 480 else if (code == "high_pressure") {weather_tiles.set_high_pressure_tile();} 481 else if (code == "high_pressure_border") {weather_tiles.set_high_pressure_border_tile();} 482 else if (code == "low_pressure_border") {weather_tiles.set_low_pressure_border_tile();} 483 else if (code == "low_pressure") {weather_tiles.set_low_pressure_tile();} 484 else if (code == "low_pressure_core") {weather_tiles.set_low_pressure_core_tile();} 485 else if (code == "cold_sector") {weather_tiles.set_cold_sector_tile();} 486 else if (code == "warm_sector") {weather_tiles.set_warm_sector_tile();} 487 else if (code == "tropical_weather") {weather_tiles.set_tropical_weather_tile();} 488 else if (code == "thunderstorms") {weather_tiles.set_thunderstorms_tile();} 489 else if (code == "test") {weather_tiles.set_4_8_stratus_tile();} 490 else 491 { 492 print("Repeat tile not implemented with this tile type!"); 493 setprop("/sim/messages/pilot", "Local weather: Repeat tile not implemented with this tile type!"); 494 } 495 } 496else if (getprop(lw~"tmp/tile-management") == "realistic weather") 497 { 498 var rn = rand() * getprop(lw~"config/large-scale-persistence"); 499 500 if (code == "low_pressure_core") 501 { 502 if (rn > 0.1) {weather_tiles.set_low_pressure_core_tile();} 503 else {weather_tiles.set_low_pressure_tile();} 504 } 505 else if (code == "low_pressure") 506 { 507 if (rn > 0.1) {weather_tiles.set_low_pressure_tile();} 508 else if (rn > 0.05) {weather_tiles.set_low_pressure_core_tile();} 509 else {weather_tiles.set_low_pressure_border_tile();} 510 } 511 else if (code == "low_pressure_border") 512 { 513 if (rn > 0.2) {weather_tiles.set_low_pressure_border_tile();} 514 else if (rn > 0.15) {weather_tiles.set_cold_sector_tile();} 515 else if (rn > 0.1) {weather_tiles.set_warm_sector_tile();} 516 else if (rn > 0.05) {weather_tiles.set_low_pressure_tile();} 517 else {weather_tiles.set_high_pressure_border_tile();} 518 } 519 else if (code == "high_pressure_border") 520 { 521 if (rn > 0.2) {weather_tiles.set_high_pressure_border_tile();} 522 else if (rn > 0.15) {weather_tiles.set_cold_sector_tile();} 523 else if (rn > 0.1) {weather_tiles.set_warm_sector_tile();} 524 else if (rn > 0.05) {weather_tiles.set_high_pressure_tile();} 525 else {weather_tiles.set_low_pressure_border_tile();} 526 } 527 else if (code == "high_pressure") 528 { 529 if (rn > 0.1) {weather_tiles.set_high_pressure_tile();} 530 else if (rn > 0.05) {weather_tiles.set_high_pressure_border_tile();} 531 else {weather_tiles.set_high_pressure_core_tile();} 532 } 533 else if (code == "high_pressure_core") 534 { 535 if (rn > 0.1) {weather_tiles.set_high_pressure_core_tile();} 536 else {weather_tiles.set_high_pressure_tile();} 537 } 538 else if (code == "cold_sector") 539 { 540 if (rn > 0.15) {weather_tiles.set_cold_sector_tile();} 541 else if (rn > 0.1) 542 { 543 if ((dir_index ==0) or (dir_index ==1) or (dir_index==2)) 544 {weather_tiles.set_warmfront1_tile();} 545 else if ((dir_index ==3) or (dir_index ==5)) 546 {weather_tiles.set_cold_sector_tile();} 547 else if ((dir_index ==6) or (dir_index ==7) or (dir_index==8)) 548 {weather_tiles.set_coldfront_tile();} 549 } 550 else if (rn > 0.05) {weather_tiles.set_low_pressure_border_tile();} 551 else {weather_tiles.set_high_pressure_border_tile();} 552 } 553 else if (code == "warm_sector") 554 { 555 if (rn > 0.15) {weather_tiles.set_warm_sector_tile();} 556 else if (rn > 0.1) 557 { 558 if ((dir_index ==0) or (dir_index ==1) or (dir_index==2)) 559 {weather_tiles.set_coldfront_tile();} 560 else if ((dir_index ==3) or (dir_index ==5)) 561 {weather_tiles.set_warm_sector_tile();} 562 else if ((dir_index ==6) or (dir_index ==7) or (dir_index==8)) 563 {weather_tiles.set_warmfront4_tile();} 564 } 565 else if (rn > 0.05) {weather_tiles.set_low_pressure_border_tile();} 566 else {weather_tiles.set_high_pressure_border_tile();} 567 } 568 else if (code == "warmfront1") 569 { 570 if ((dir_index ==0) or (dir_index ==1) or (dir_index==2)) 571 {weather_tiles.set_warmfront2_tile();} 572 else if ((dir_index ==3) or (dir_index ==5)) 573 { 574 if (rand() > 0.15) 575 {weather_tiles.set_warmfront1_tile();} 576 else 577 {weather_tiles.set_high_pressure_border_tile();} 578 } 579 else if ((dir_index ==6) or (dir_index ==7) or (dir_index==8)) 580 {weather_tiles.set_cold_sector_tile();} 581 } 582 else if (code == "warmfront2") 583 { 584 if ((dir_index ==0) or (dir_index ==1) or (dir_index==2)) 585 {weather_tiles.set_warmfront3_tile();} 586 if ((dir_index ==3) or (dir_index ==5)) 587 { 588 if (rand() > 0.15) 589 {weather_tiles.set_warmfront2_tile();} 590 else 591 {weather_tiles.set_high_pressure_border_tile();} 592 } 593 if ((dir_index ==6) or (dir_index ==7) or (dir_index==8)) 594 {weather_tiles.set_warmfront1_tile();} 595 } 596 else if (code == "warmfront3") 597 { 598 if ((dir_index ==0) or (dir_index ==1) or (dir_index==2)) 599 {weather_tiles.set_warmfront4_tile();} 600 if ((dir_index ==3) or (dir_index ==5)) 601 { 602 if (rand() > 0.15) 603 {weather_tiles.set_warmfront3_tile();} 604 else 605 {weather_tiles.set_low_pressure_border_tile();} 606 } 607 if ((dir_index ==6) or (dir_index ==7) or (dir_index==8)) 608 {weather_tiles.set_warmfront2_tile();} 609 } 610 else if (code == "warmfront4") 611 { 612 if ((dir_index ==0) or (dir_index ==1) or (dir_index==2)) 613 {weather_tiles.set_warm_sector_tile();} 614 if ((dir_index ==3) or (dir_index ==5)) 615 { 616 if (rand() > 0.15) 617 {weather_tiles.set_warmfront4_tile();} 618 else 619 {weather_tiles.set_low_pressure_tile();} 620 } 621 if ((dir_index ==6) or (dir_index ==7) or (dir_index==8)) 622 {weather_tiles.set_warmfront3_tile();} 623 } 624 else if (code == "coldfront") 625 { 626 if ((dir_index ==0) or (dir_index ==1) or (dir_index==2)) 627 {weather_tiles.set_cold_sector_tile();} 628 else if ((dir_index ==3) or (dir_index ==5)) 629 { 630 if (rand() > 0.15) 631 {weather_tiles.set_coldfront_tile();} 632 else 633 {weather_tiles.set_high_pressure_border_tile();} 634 } 635 else if ((dir_index ==6) or (dir_index ==7) or (dir_index==8)) 636 {weather_tiles.set_warm_sector_tile();} 637 } 638 else 639 { 640 print("Realistic weather not implemented with this tile type!"); 641 setprop("/sim/messages/pilot", "Local weather: Realistic weather not implemented with this tile type!"); 642 } 643 644 } # end if mode == realistic weather 645else if (getprop(lw~"tmp/tile-management") == "METAR") 646 { 647 weather_tiles.set_METAR_tile(); 648 } 649} 650 651 652################################### 653# tile removal call 654################################### 655 656var remove_tile = func (index) { 657 658# remove tile from active list 659 660var s = size(active_tile_list); 661 662for (var j = 0; j < s; j=j+1) 663 { 664 if (index == active_tile_list[j]) 665 { 666 active_tile_list = delete_from_vector(active_tile_list,j); 667 break; 668 } 669 } 670 671settimer( func { props.globals.getNode("local-weather/clouds", 1).removeChild("tile",index) },100); 672 673 674var effectNode = props.globals.getNode("local-weather/effect-volumes").getChildren("effect-volume"); 675 676var ecount = 0; 677 678for (var i = 0; i < local_weather.n_effectVolumeArray; i = i + 1) 679 { 680 ev = local_weather.effectVolumeArray[i]; 681 if (ev.index == index) 682 { 683 local_weather.effectVolumeArray = delete_from_vector(local_weather.effectVolumeArray,i); 684 local_weather.n_effectVolumeArray = local_weather.n_effectVolumeArray - 1; 685 i = i - 1; 686 ecount = ecount + 1; 687 } 688 else if (ev.index == 0) # use the opportunity to check if static effects should also be removed 689 { 690 if (ev.get_distance() > 80000.0) 691 { 692 local_weather.effectVolumeArray = delete_from_vector(local_weather.effectVolumeArray,i); 693 local_weather.n_effectVolumeArray = local_weather.n_effectVolumeArray - 1; 694 i = i - 1; 695 ecount = ecount + 1; 696 } 697 } 698 } 699 700 701setprop(lw~"effect-volumes/number",getprop(lw~"effect-volumes/number")- ecount); 702 703# set placement indices to zero to reinitiate search for free positions 704 705setprop(lw~"clouds/placement-index",0); 706setprop(lw~"clouds/model-placement-index",0); 707setprop(lw~"effect-volumes/effect-placement-index",0); 708 709# remove quadtree structures 710 711if (local_weather.dynamics_flag ==1) 712 { 713 settimer( func {setsize(weather_dynamics.cloudQuadtrees[index-1],0);},1.0); 714 } 715 716# rebuild effect volume vector 717 718local_weather.assemble_effect_array(); 719 720if (local_weather.wxradar_support_flag == 1) 721 { 722 local_weather.remove_wxradar_echos(); 723 } 724 725} 726 727 728 729################################### 730# active tile change and neighbour 731# recomputation 732################################### 733 734var change_active_tile = func (index) { 735 736var t = props.globals.getNode(lw~"tiles").getChild("tile",index,0); 737 738var lat = t.getNode("latitude-deg").getValue(); 739var lon = t.getNode("longitude-deg").getValue(); 740# var alpha = getprop(lw~"tmp/tile-orientation-deg"); 741 742var alpha_old = getprop(lw~"tiles/tile[4]/orientation-deg"); 743var alpha = t.getNode("orientation-deg").getValue(); 744 745var coordinate_rotation_angle = norm_relangle(alpha_old, alpha); 746 747if (coordinate_rotation_angle < 45.0) 748 { 749 var system_rotation_angle = 0; 750 var displacement_angle = coordinate_rotation_angle; 751 } 752else if (coordinate_rotation_angle < 135.0) 753 { 754 var system_rotation_angle = 90.0; 755 var displacement_angle = coordinate_rotation_angle - 90.0; 756 } 757else if (coordinate_rotation_angle < 225.0) 758 { 759 var system_rotation_angle = 180.0; 760 var displacement_angle = coordinate_rotation_angle - 180.0; 761 } 762else if (coordinate_rotation_angle < 315.0) 763 { 764 var system_rotation_angle = 270.0; 765 var displacement_angle = coordinate_rotation_angle - 270.0; 766 } 767else 768 { 769 var system_rotation_angle = 0; 770 var displacement_angle = coordinate_rotation_angle - 360.0; 771 } 772 773alpha = alpha_old + displacement_angle; 774 775if (index == 0) 776 { 777 copy_entry(4,8); 778 copy_entry(3,7); 779 copy_entry(1,5); 780 copy_entry(0,4); 781 create_neighbour(lat,lon,0,alpha); 782 create_neighbour(lat,lon,1,alpha); 783 create_neighbour(lat,lon,2,alpha); 784 create_neighbour(lat,lon,3,alpha); 785 create_neighbour(lat,lon,6,alpha); 786 } 787else if (index == 1) 788 { 789 copy_entry(3,6); 790 copy_entry(4,7); 791 copy_entry(5,8); 792 copy_entry(0,3); 793 copy_entry(1,4); 794 copy_entry(2,5); 795 create_neighbour(lat,lon,0,alpha); 796 create_neighbour(lat,lon,1,alpha); 797 create_neighbour(lat,lon,2,alpha); 798 } 799else if (index == 2) 800 { 801 copy_entry(4,6); 802 copy_entry(1,3); 803 copy_entry(2,4); 804 copy_entry(5,7); 805 create_neighbour(lat,lon,0,alpha); 806 create_neighbour(lat,lon,1,alpha); 807 create_neighbour(lat,lon,2,alpha); 808 create_neighbour(lat,lon,5,alpha); 809 create_neighbour(lat,lon,8,alpha); 810 } 811else if (index == 3) 812 { 813 copy_entry(1,2); 814 copy_entry(4,5); 815 copy_entry(7,8); 816 copy_entry(0,1); 817 copy_entry(3,4); 818 copy_entry(6,7); 819 create_neighbour(lat,lon,0,alpha); 820 create_neighbour(lat,lon,3,alpha); 821 create_neighbour(lat,lon,6,alpha); 822 } 823else if (index == 5) 824 { 825 copy_entry(1,0); 826 copy_entry(4,3); 827 copy_entry(7,6); 828 copy_entry(2,1); 829 copy_entry(5,4); 830 copy_entry(8,7); 831 create_neighbour(lat,lon,2,alpha); 832 create_neighbour(lat,lon,5,alpha); 833 create_neighbour(lat,lon,8,alpha); 834 } 835else if (index == 6) 836 { 837 copy_entry(4,2); 838 copy_entry(3,1); 839 copy_entry(6,4); 840 copy_entry(7,5); 841 create_neighbour(lat,lon,0,alpha); 842 create_neighbour(lat,lon,3,alpha); 843 create_neighbour(lat,lon,6,alpha); 844 create_neighbour(lat,lon,7,alpha); 845 create_neighbour(lat,lon,8,alpha); 846 } 847else if (index == 7) 848 { 849 copy_entry(3,0); 850 copy_entry(4,1); 851 copy_entry(5,2); 852 copy_entry(6,3); 853 copy_entry(7,4); 854 copy_entry(8,5); 855 create_neighbour(lat,lon,6,alpha); 856 create_neighbour(lat,lon,7,alpha); 857 create_neighbour(lat,lon,8,alpha); 858 } 859else if (index == 8) 860 { 861 copy_entry(4,0); 862 copy_entry(7,3); 863 copy_entry(8,4); 864 copy_entry(5,1); 865 create_neighbour(lat,lon,2,alpha); 866 create_neighbour(lat,lon,5,alpha); 867 create_neighbour(lat,lon,6,alpha); 868 create_neighbour(lat,lon,7,alpha); 869 create_neighbour(lat,lon,8,alpha); 870 } 871 872 873 874 875if (system_rotation_angle > 0.0) 876 { 877 if (local_weather.debug_output_flag == 1) 878 {print("Rotating coordinate system by ", system_rotation_angle, " degrees");} 879 880 # create a buffer entry for rotation, this is deleted in the rotation routine 881 882 create_neighbour(lat, lon, 9, alpha); 883 rotate_tile_scheme(system_rotation_angle); 884 } 885 886# ready the system for creating impostors 887 888impostor_trigger = 1; 889 890} 891 892 893################################### 894# rotate tile scheme 895################################### 896 897var rotate_tile_scheme = func (angle) { 898 899if (angle < 45.0) 900 { 901 return; 902 } 903else if (angle < 135) 904 { 905 906 907 copy_entry(2,9); 908 copy_entry(8,2); 909 copy_entry(6,8); 910 copy_entry(0,6); 911 copy_entry(9,0); 912 copy_entry(5,9); 913 copy_entry(7,5); 914 copy_entry(3,7); 915 copy_entry(1,3); 916 copy_entry(9,1); 917 918 props.globals.getNode(lw~"tiles").removeChild("tile",9); 919 } 920else if (angle < 225) 921 { 922 copy_entry(8,9); 923 copy_entry(0,8); 924 copy_entry(9,0); 925 926 copy_entry(7,9); 927 copy_entry(1,7); 928 copy_entry(9,1); 929 930 copy_entry(6,9); 931 copy_entry(2,6); 932 copy_entry(9,2); 933 934 copy_entry(5,9); 935 copy_entry(3,5); 936 copy_entry(9,3); 937 938 props.globals.getNode(lw~"tiles").removeChild("tile",9); 939 } 940else if (angle < 315) 941 { 942 copy_entry(0,9); 943 copy_entry(6,0); 944 copy_entry(8,6); 945 copy_entry(2,8); 946 copy_entry(9,2); 947 copy_entry(3,9); 948 copy_entry(7,3); 949 copy_entry(5,7); 950 copy_entry(1,5); 951 copy_entry(9,1); 952 953 props.globals.getNode(lw~"tiles").removeChild("tile",9); 954 } 955else 956 { 957 return; 958 } 959} 960 961 962##################################### 963# copy tile info in neighbour matrix 964##################################### 965 966var copy_entry = func (from_index, to_index) { 967 968var tNode = props.globals.getNode(lw~"tiles"); 969 970var f = tNode.getChild("tile",from_index,0); 971var t = tNode.getChild("tile",to_index,0); 972 973t.getNode("latitude-deg").setValue(f.getNode("latitude-deg").getValue()); 974t.getNode("longitude-deg").setValue(f.getNode("longitude-deg").getValue()); 975t.getNode("generated-flag").setValue(f.getNode("generated-flag").getValue()); 976t.getNode("tile-index").setValue(f.getNode("tile-index").getValue()); 977t.getNode("timestamp-sec").setValue(f.getNode("timestamp-sec").getValue()); 978t.getNode("orientation-deg").setValue(f.getNode("orientation-deg").getValue()); 979t.getNode("code").setValue(f.getNode("code").getValue()); 980 981 982} 983 984##################################### 985# create adjacent tile coordinates 986##################################### 987 988var create_neighbour = func (blat, blon, index, alpha) { 989 990var x = 0.0; 991var y = 0.0; 992var phi = alpha * math.pi/180.0; 993 994calc_geo(blat); 995 996if ((index == 0) or (index == 3) or (index == 6)) {x =-40000.0;} 997if ((index == 2) or (index == 5) or (index == 8)) {x = 40000.0;} 998 999if ((index == 0) or (index == 1) or (index == 2)) {y = 40000.0;} 1000if ((index == 6) or (index == 7) or (index == 8)) {y = -40000.0;} 1001 1002var t = props.globals.getNode(lw~"tiles").getChild("tile",index,1); 1003 1004# use the last built tile code as default, in case a tile isn't formed when reached, 1005# the code is not empty but has a plausible value 1006 1007var default_code = getprop(lw~"tiles/code"); 1008 1009t.getNode("latitude-deg",1).setValue(blat + get_lat(x,y,phi)); 1010t.getNode("longitude-deg",1).setValue(blon + get_lon(x,y,phi)); 1011t.getNode("generated-flag",1).setValue(0); 1012t.getNode("tile-index",1).setValue(-1); 1013t.getNode("code",1).setValue(default_code); 1014t.getNode("timestamp-sec",1).setValue(weather_dynamics.time_lw); 1015t.getNode("orientation-deg",1).setValue(0.0); 1016} 1017 1018##################################### 1019# find the 8 adjacent tile coordinates 1020# after the initial setup call 1021##################################### 1022 1023var create_neighbours = func (blat, blon, alpha) { 1024 1025var x = 0.0; 1026var y = 0.0; 1027var phi = alpha * math.pi/180.0; 1028 1029calc_geo(blat); 1030 1031x = -40000.0; y = 40000.0; 1032setprop(lw~"tiles/tile[0]/latitude-deg",blat + get_lat(x,y,phi)); 1033setprop(lw~"tiles/tile[0]/longitude-deg",blon + get_lon(x,y,phi)); 1034setprop(lw~"tiles/tile[0]/generated-flag",0); 1035setprop(lw~"tiles/tile[0]/tile-index",-1); 1036setprop(lw~"tiles/tile[0]/code",""); 1037setprop(lw~"tiles/tile[0]/timestamp-sec",weather_dynamics.time_lw); 1038setprop(lw~"tiles/tile[0]/orientation-deg",alpha); 1039 1040x = 0.0; y = 40000.0; 1041setprop(lw~"tiles/tile[1]/latitude-deg",blat + get_lat(x,y,phi)); 1042setprop(lw~"tiles/tile[1]/longitude-deg",blon + get_lon(x,y,phi)); 1043setprop(lw~"tiles/tile[1]/generated-flag",0); 1044setprop(lw~"tiles/tile[1]/tile-index",-1); 1045setprop(lw~"tiles/tile[1]/code",""); 1046setprop(lw~"tiles/tile[1]/timestamp-sec",weather_dynamics.time_lw); 1047setprop(lw~"tiles/tile[1]/orientation-deg",alpha); 1048 1049x = 40000.0; y = 40000.0; 1050setprop(lw~"tiles/tile[2]/latitude-deg",blat + get_lat(x,y,phi)); 1051setprop(lw~"tiles/tile[2]/longitude-deg",blon + get_lon(x,y,phi)); 1052setprop(lw~"tiles/tile[2]/generated-flag",0); 1053setprop(lw~"tiles/tile[2]/tile-index",-1); 1054setprop(lw~"tiles/tile[2]/code",""); 1055setprop(lw~"tiles/tile[2]/timestamp-sec",weather_dynamics.time_lw); 1056setprop(lw~"tiles/tile[2]/orientation-deg",alpha); 1057 1058x = -40000.0; y = 0.0; 1059setprop(lw~"tiles/tile[3]/latitude-deg",blat + get_lat(x,y,phi)); 1060setprop(lw~"tiles/tile[3]/longitude-deg",blon + get_lon(x,y,phi)); 1061setprop(lw~"tiles/tile[3]/generated-flag",0); 1062setprop(lw~"tiles/tile[3]/tile-index",-1); 1063setprop(lw~"tiles/tile[3]/code",""); 1064setprop(lw~"tiles/tile[3]/timestamp-sec",weather_dynamics.time_lw); 1065setprop(lw~"tiles/tile[3]/orientation-deg",alpha); 1066 1067# this is the current tile 1068x = 0.0; y = 0.0; 1069setprop(lw~"tiles/tile[4]/latitude-deg",blat + get_lat(x,y,phi)); 1070setprop(lw~"tiles/tile[4]/longitude-deg",blon + get_lon(x,y,phi)); 1071setprop(lw~"tiles/tile[4]/generated-flag",1); 1072setprop(lw~"tiles/tile[4]/tile-index",1); 1073setprop(lw~"tiles/tile[4]/code",""); 1074setprop(lw~"tiles/tile[4]/timestamp-sec",weather_dynamics.time_lw); 1075setprop(lw~"tiles/tile[4]/orientation-deg",getprop(lw~"tmp/tile-orientation-deg")); 1076 1077 1078x = 40000.0; y = 0.0; 1079setprop(lw~"tiles/tile[5]/latitude-deg",blat + get_lat(x,y,phi)); 1080setprop(lw~"tiles/tile[5]/longitude-deg",blon + get_lon(x,y,phi)); 1081setprop(lw~"tiles/tile[5]/generated-flag",0); 1082setprop(lw~"tiles/tile[5]/tile-index",-1); 1083setprop(lw~"tiles/tile[5]/code",""); 1084setprop(lw~"tiles/tile[5]/timestamp-sec",weather_dynamics.time_lw); 1085setprop(lw~"tiles/tile[5]/orientation-deg",alpha); 1086 1087x = -40000.0; y = -40000.0; 1088setprop(lw~"tiles/tile[6]/latitude-deg",blat + get_lat(x,y,phi)); 1089setprop(lw~"tiles/tile[6]/longitude-deg",blon + get_lon(x,y,phi)); 1090setprop(lw~"tiles/tile[6]/generated-flag",0); 1091setprop(lw~"tiles/tile[6]/tile-index",-1); 1092setprop(lw~"tiles/tile[6]/code",""); 1093setprop(lw~"tiles/tile[6]/timestamp-sec",weather_dynamics.time_lw); 1094setprop(lw~"tiles/tile[6]/orientation-deg",alpha); 1095 1096x = 0.0; y = -40000.0; 1097setprop(lw~"tiles/tile[7]/latitude-deg",blat + get_lat(x,y,phi)); 1098setprop(lw~"tiles/tile[7]/longitude-deg",blon + get_lon(x,y,phi)); 1099setprop(lw~"tiles/tile[7]/generated-flag",0); 1100setprop(lw~"tiles/tile[7]/tile-index",-1); 1101setprop(lw~"tiles/tile[7]/code",""); 1102setprop(lw~"tiles/tile[7]/timestamp-sec",weather_dynamics.time_lw); 1103setprop(lw~"tiles/tile[7]/orientation-deg",alpha); 1104 1105x = 40000.0; y = -40000.0; 1106setprop(lw~"tiles/tile[8]/latitude-deg",blat + get_lat(x,y,phi)); 1107setprop(lw~"tiles/tile[8]/longitude-deg",blon + get_lon(x,y,phi)); 1108setprop(lw~"tiles/tile[8]/generated-flag",0); 1109setprop(lw~"tiles/tile[8]/tile-index",-1); 1110setprop(lw~"tiles/tile[8]/code",""); 1111setprop(lw~"tiles/tile[8]/timestamp-sec",weather_dynamics.time_lw); 1112setprop(lw~"tiles/tile[8]/orientation-deg",alpha); 1113} 1114 1115 1116 1117 1118############################### 1119# housekeeping loop 1120############################### 1121 1122var housekeeping_loop = func (index, index1) { 1123 1124if (local_weather.local_weather_running_flag == 0) {return;} 1125 1126# supply a few properties which are used more generally 1127 1128lwObserverLat = getprop("/position/latitude-deg"); 1129lwObserverLon = getprop("/position/longitude-deg"); 1130 1131var heading = getprop("/orientation/heading-deg"); 1132var offset = getprop("/sim/current-view/heading-offset-deg"); 1133lwViewDir = (heading - offset) * math.pi/180.0 ; 1134setsize(lwViewVec,0); 1135append(lwViewVec,math.cos(lwViewDir)); 1136append(lwViewVec,-math.sin(lwViewDir)); 1137 1138 1139var n = 5; 1140var n_max = size(cloudSceneryArray); 1141n_cloudSceneryArray = n_max; 1142var s = size(active_tile_list); 1143 1144var m_max = size(cloudArray); 1145 1146setprop(lw~"clouds/cloud-scenery-count",n_max+m_max); 1147 1148# don't do anything as long as the array is empty 1149 1150if ((n_max == 0) and (m_max == 0)) # nothing to do, loop over 1151 {if (getprop(lw~"housekeeping-loop-flag") ==1) {settimer( func {housekeeping_loop(index, index1)}, 0);} return;} 1152 1153# parse the flags 1154 1155 1156# now process the Scenery array 1157 1158if (index > n_max-1) {index = 0;} 1159 1160var i_max = index + n; 1161if (i_max > n_max) {i_max = n_max;} 1162 1163for (var i = index; i < i_max; i = i+1) 1164 { 1165 var c = cloudSceneryArray[i]; 1166 1167 var flag = 0; 1168 1169 for (var j = 0; j < s; j = j+1) 1170 { 1171 if (active_tile_list[j] == c.index) {flag = 1; break;} 1172 if (c.index == 0) {flag =1; break;} # clouds in tile index 0 are special 1173 } 1174 1175 if (flag == 0) 1176 { 1177 c.removeNodes(); 1178 cloudSceneryArray = delete_from_vector(cloudSceneryArray,i); 1179 i = i -1; i_max = i_max - 1; n_max = n_max - 1; 1180 n_cloudSceneryArray = n_cloudSceneryArray -1; 1181 continue; 1182 } 1183 } 1184 1185 1186# now process the hard coded cloud array and see a tile has been removed 1187 1188if (index1 > m_max-1) {index1 = 0;} 1189 1190var j_max = index1 + n; 1191if (j_max > m_max) {j_max = m_max;} 1192 1193for (var j = index1; j < j_max; j = j+1) 1194 { 1195 var c = cloudArray[j]; 1196 1197 var flag = 0; 1198 1199 for (var k = 0; k < s; k = k+1) 1200 { 1201 if (active_tile_list[k] == c.index) {flag = 1; break;} 1202 } 1203 1204 if (flag == 0) 1205 { 1206 c.remove(); 1207 cloudArray = delete_from_vector(cloudArray,j); 1208 j = j -1; j_max = j_max - 1; m_max = m_max - 1; 1209 continue; 1210 } 1211 } 1212 1213if (getprop(lw~"housekeeping-loop-flag") ==1) {settimer( func {housekeeping_loop(i,j)}, 0);} 1214} 1215 1216 1217############################### 1218# impostor handline routines 1219############################### 1220 1221var impostor_trigger = 0; 1222 1223var impostor_type_map = {"low_pressure_core" : "Nimbus", "low_pressure" : "broken", "low_pressure_border": "broken", "high_pressure_border" : "scattered", "high_pressure" : "few", "high_pressure_core": "few"}; 1224 1225var remove_impostors = func { 1226 1227foreach (entry;cloudImpostorArray) 1228 { 1229 entry.removeNodes(); 1230 } 1231setsize(cloudImpostorArray,0); 1232} 1233 1234var create_impostors = func { 1235 1236var visibility = getprop("/environment/visibility-m"); 1237var cloud_range = getprop("/sim/rendering/clouds3d-vis-range"); 1238 1239if ((visibility < 80000.0) or (cloud_range < 70000.0)) {return;} 1240 1241if (visibility < cloud_range) {var range = visibility;} 1242else {var range = cloud_range;} 1243 1244var n = int ((range - 60000.0)/40000.0); 1245 1246var lat = getprop(lw~"tiles/tile[4]/latitude-deg"); 1247var lon = getprop(lw~"tiles/tile[4]/longitude-deg"); 1248var alpha = getprop(lw~"tiles/tile[4]/orientation-deg"); 1249var code = getprop(lw~"tiles/tile[4]/code"); 1250var index = getprop(lw~"tiles/tile[4]/tile-index"); 1251var alt_offset = getprop(lw~"tmp/tile-alt-offset-ft"); 1252var alt = weather_dynamics.tile_convective_altitude[index-1] + 1000.0 + alt_offset; 1253 1254if (contains(impostor_type_map,code)) {var type = impostor_type_map[code];} 1255else {var type = "scattered";} 1256 1257if (local_weather.debug_output_flag == 1) 1258 { 1259 printf("Creating impostor ring..."); 1260 print(lat, " ", lon, " ", alt, " ", alpha, " ", type, " ", n); 1261 } 1262 1263weather_tiles.create_impostor_ring(lat, lon, alt, alpha, type, n); 1264} 1265 1266################################## 1267# Thunderstorm positon management 1268################################## 1269 1270var thunderstorm_management_loop = func { 1271 1272if (local_weather.local_weather_running_flag == 0) {return;} 1273 1274# compute some general-purpose stuff for the loop 1275 1276var eyeLat = lwObserverLat; 1277var eyeLon = lwObserverLon; 1278 1279var n = size(thunderstormArray); 1280 1281#print("We have ",n," storms."); 1282 1283for (var i = 0; i < n; i=i+1) 1284 { 1285 var tstorm = thunderstormArray[i]; 1286 1287 var rn = rand(); 1288 #if (i == 0) {rn = 0;} else {rn = 1;} 1289 if (rn < tstorm.strength) 1290 { 1291 #print("Lightning strike storm ", i,"!"); 1292 1293 var diffx = -(tstorm.lat - eyeLat) * local_weather.lat_to_m; 1294 var diffy = (tstorm.lon - eyeLon) * local_weather.lon_to_m ; 1295 var offset_x = -3000.0 + rand() * 6000.0; 1296 var offset_y = -3000.0 + rand() * 6000.0; 1297 1298 var dist = math.sqrt(diffx * diffx + diffy * diffy) ; 1299 1300 setprop("/environment/lightning/lightning-pos-x", diffx + offset_x); 1301 setprop("/environment/lightning/lightning-pos-y", diffy + offset_y); 1302 setprop("/environment/lightning/lightning-range", tstorm.size); 1303 setprop("/local-weather/lightning/latitude-deg", tstorm.lat - offset_x * local_weather.m_to_lat); 1304 setprop("/local-weather/lightning/longitude-deg", tstorm.lon + offset_y * local_weather.m_to_lon); 1305 setprop("/local-weather/lightning/altitude-ft", tstorm.alt); 1306 1307 var rn = rand(); 1308 var type = 0; 1309 if (rn > 0.7) {type = 1;} 1310 else if (rn > 0.3) {type = 2;} 1311 setprop("/local-weather/lightning/model-index", type); 1312 1313 lightning_strike(); 1314 1315 if (dist > 50000.0) 1316 { 1317 thunderstormArray = delete_from_vector(thunderstormArray,i); 1318 print("Removing storm ", i); 1319 break; 1320 } 1321 } 1322 1323 1324 } 1325 1326 1327if (getprop(lw~"thunderstorm-loop-flag") ==1) {settimer( func {thunderstorm_management_loop()}, 1.0);} 1328} 1329 1330var lightning_strike = func { 1331 1332var rn = rand(); 1333 1334var repeat = 1; 1335 1336if (rn > 0.5) {repeat = 2;} 1337 1338var duration = 0.1 + 0.1 * rand(); 1339var strength = 0.5 + 1.0 * rand(); 1340 1341setprop("/environment/lightning/flash", strength); 1342settimer( func{ setprop("/environment/lightning/flash", 0.0);}, duration); 1343 1344var duration1 = 0.1 + 0.1 * rand(); 1345 1346if (repeat == 2) 1347 { 1348 settimer( func{ setprop("/environment/lightning/flash", strength);}, duration + 0.1); 1349 settimer( func{ setprop("/environment/lightning/flash", 0.0);}, duration + 0.1 + duration1); 1350 } 1351 1352} 1353 1354############################### 1355# Cloud shadow management 1356############################### 1357 1358 1359 1360var shadow_management_loop = func (index) { 1361 1362if (local_weather.local_weather_running_flag == 0) {return;} 1363 1364var n = 50; 1365var n_max = size(cloudShadowCandidateArray); 1366var s = size(active_tile_list); 1367 1368# don't do anything as long as the array is empty 1369 1370if (n_max == 0) # nothing to do, loop over 1371 { 1372 setprop("/local-weather/cloud-shadows/cloud-shadow-flag",0); 1373 if (getprop(lw~"shadow-loop-flag") ==1) {settimer( func {shadow_management_loop(index)}, 0);} 1374 return; 1375 } 1376else 1377 { 1378 setprop("/local-weather/cloud-shadows/cloud-shadow-flag",1); 1379 } 1380 1381 1382 1383# compute some general-purpose stuff for the loop 1384 1385var eyeLat = lwObserverLat; 1386var eyeLon = lwObserverLon; 1387 1388#var sec_to_rad = 2.0 * math.pi/86400; 1389var time = getprop("/sim/time/utc/day-seconds") + getprop("/sim/time/local-offset"); 1390var sun_angle = getprop("/sim/time/sun-angle-rad"); 1391var cloud_alt = getprop("/environment/ground-haze-thickness-m"); 1392var offset_mag = cloud_alt * math.tan(sun_angle); 1393var offset_x = -math.cos(time * local_weather.sec_to_rad) * math.cos(eyeLat) * offset_mag; 1394var offset_y = -math.sin(time * local_weather.sec_to_rad) * math.sin(eyeLat) * offset_mag; 1395 1396 1397# indexing, we don't want to run through hundreds of candidates per frame 1398 1399if (index > n_max-1) {index = 0;} 1400 1401var i_max = index + n; 1402if (i_max > n_max) {i_max = n_max;} 1403 1404for (var i = index; i < i_max; i = i+1) 1405 { 1406 var shadow = cloudShadowCandidateArray[i]; 1407 1408 # housekeeping - delete shadows from deleted tiles 1409 1410 var flag = 0; 1411 1412 for (var j = 0; j < s; j = j+1) 1413 { 1414 if (active_tile_list[j] == shadow.index) {flag = 1; break;} 1415 if (shadow.index == 0) {flag =1; break;} # clouds in tile index 0 are special 1416 } 1417 if (flag == 0) 1418 { 1419 #print("Shadow management housekeeping!"); 1420 cloudShadowCandidateArray = delete_from_vector(cloudShadowCandidateArray,i); 1421 i = i -1; i_max = i_max - 1; n_max = n_max - 1; 1422 continue; 1423 } 1424 1425 # find nearest shadows 1426 1427 if (shadow.shadow_flag == 0) 1428 { 1429 var diffx = (shadow.lat - eyeLat) * local_weather.lat_to_m + offset_x; 1430 var diffy = -(shadow.lon - eyeLon) * local_weather.lon_to_m + offset_y; 1431 var dist = math.sqrt(diffx * diffx + diffy * diffy) ; 1432 if (getprop("/local-weather/cloud-shadows/cloud-shadow-fov-flag")==1) 1433 { 1434 var viewDotPos = (diffx * lwViewVec[0] + diffy * lwViewVec[1])/dist; 1435 if (viewDotPos <0.7) {dist = dist -(viewDotPos - 0.7) * 10000.0;} 1436 } 1437 if (dist < cloudShadowMaxDist) 1438 { 1439 #print("Shadow management:"); 1440 #print("Max. dist is now: ", dist); 1441 #print("Adding cloud"); 1442 #print("Array size is now: ", size(cloudShadowArray)); 1443 #print("CloudShadowMinIndex is now: ", cloudShadowMinIndex); 1444 cloudShadowMaxDist = dist; 1445 if (size(cloudShadowArray)>cloudShadowArraySize-1) 1446 { 1447 cloudShadowArray[cloudShadowMinIndex].shadow_flag = 0; 1448 cloudShadowArray = delete_from_vector(cloudShadowArray,cloudShadowMinIndex); 1449 } 1450 append(cloudShadowArray,shadow); 1451 shadow.shadow_flag = 1; 1452 break; 1453 } 1454 } 1455 } 1456 1457var index_max = -1; 1458var index_min = -1; 1459var dist_max = -1.0; 1460var dist_min = 1000000.0; 1461 1462var counter = 0; 1463 1464var diffx = 0.0; 1465var diffy = 0.0; 1466 1467foreach(s; cloudShadowArray) 1468 { 1469 diffx = (s.lat - eyeLat) * local_weather.lat_to_m + offset_x; 1470 diffy = -(s.lon - eyeLon) * local_weather.lon_to_m + offset_y; 1471 1472 var dist = math.sqrt(diffx*diffx + diffy*diffy); 1473 if (getprop("/local-weather/cloud-shadows/cloud-shadow-fov-flag")==1) 1474 { 1475 var viewDotPos = (diffx * lwViewVec[0] + diffy * lwViewVec[1])/dist; 1476 if (viewDotPos <0.7) {dist = dist -(viewDotPos - 0.7) * 10000.0;} 1477 } 1478 if (dist > dist_max) {dist_max = dist; index_max = counter;} 1479 if (dist < dist_min) {dist_min = dist; index_min = counter;} 1480 1481 setprop("/local-weather/cloud-shadows/cloudpos-x["~counter~"]",int(diffx) + s.size); 1482 setprop("/local-weather/cloud-shadows/cloudpos-y["~counter~"]",int(diffy) + s.strength ); 1483 counter = counter+1; 1484 } 1485 1486 # now write out the closest cloud for the detail effects 1487 if (index_min != -1) { 1488 var s = cloudShadowArray[index_min]; 1489 1490 diffx = (s.lat - eyeLat) * local_weather.lat_to_m + offset_x; 1491 diffy = -(s.lon - eyeLon) * local_weather.lon_to_m + offset_y; 1492 1493 setprop("/local-weather/cloud-shadows/nearest-cloudpos-x",int(diffx) + s.size); 1494 setprop("/local-weather/cloud-shadows/nearest-cloudpos-y",int(diffy) + s.strength ); 1495 1496 1497 #print("Dist_max:", dist_max, " index_max: ", index_max); 1498 cloudShadowMinIndex = index_max; 1499 if (dist_max > 0.0) {cloudShadowMaxDist = dist_max;} 1500 } 1501 1502 settimer( func {shadow_management_loop(i)}, 0); 1503} 1504 1505 1506 1507############################### 1508# watchdog loop for debugging 1509############################### 1510 1511 1512var watchdog_loop = func { 1513 1514var winddir = getprop("/environment/wind-from-heading-deg"); 1515var windspeed = getprop("/environment/wind-speed-kt"); 1516 1517print (windspeed, " ", winddir); 1518 1519 1520if (0==1) 1521{var tNode = props.globals.getNode(lw~"tiles", 1).getChildren("tile"); 1522 1523var i = 0; 1524 1525print("===================="); 1526 1527var viewpos = geo.aircraft_position(); 1528var n_stations = size(local_weather.weatherStationArray); 1529 1530var sum_T = 0.0; 1531var sum_p = 0.0; 1532var sum_D = 0.0; 1533var sum_norm = 0.0; 1534 1535var sum_wind = [0,0]; 1536 1537var wsize = size(local_weather.windIpointArray); 1538 1539var alt = getprop("position/altitude-ft"); 1540 1541for (var i = 0; i < wsize; i=i+1) { 1542 1543 1544 var w = local_weather.windIpointArray[i]; 1545 1546 var wpos = geo.Coord.new(); 1547 wpos.set_latlon(w.lat,w.lon,1000.0); 1548 1549 1550 1551 var d = viewpos.distance_to(wpos); 1552 if (d <100.0) {d = 100.0;} # to prevent singularity at zero 1553 1554 sum_norm = sum_norm + (1./d); 1555 1556 var res = local_weather.wind_altitude_interpolation(alt,w); 1557 1558 sum_wind = local_weather.add_vectors(sum_wind[0], sum_wind[1], res[0], (res[1]/d)); 1559 1560 print(i, " dir: ", res[0], " speed: ", res[1], " d: ",d); 1561 } 1562 1563print("dir_int: ", sum_wind[0], " speed_int: ", sum_wind[1]/sum_norm); 1564} 1565 1566 1567if (0==1) 1568{ 1569for (var i = 0; i < n_stations; i=i+1) { 1570 1571 s = local_weather.weatherStationArray[i]; 1572 1573 1574 var stpos = geo.Coord.new(); 1575 stpos.set_latlon(s.lat,s.lon,0.0); 1576 1577 var d = viewpos.distance_to(stpos); 1578 if (d <100.0) {d = 100.0;} # to prevent singularity at zero 1579 1580 sum_norm = sum_norm + 1./d * s.weight; 1581 1582 sum_T = sum_T + (s.T/d) * s.weight; 1583 sum_D = sum_D + (s.D/d) * s.weight; 1584 sum_p = sum_p + (s.p/d) * s.weight; 1585 1586 print(i, " p: ", s.p, " T: ", s.T, " D: ", s.D, " d: ",d); 1587 1588 } 1589 1590print("p_int: ", sum_p/sum_norm, " T_int: ", sum_T/sum_norm, " D_int: ", sum_D/sum_norm); 1591} 1592 1593if (0==1) 1594{ 1595 1596foreach(t; tNode) 1597 { 1598 var code = t.getNode("code").getValue(); 1599 var index = t.getNode("tile-index").getValue(); 1600 var flag = t.getNode("generated-flag").getValue(); 1601 var alpha = t.getNode("orientation-deg").getValue(); 1602 1603 print(i,": code: ", code, " unique id: ", index, " flag: ", flag, " alpha: ",alpha); 1604 1605 i = i + 1; 1606 } 1607print("alpha: ",getprop(lw~"tmp/tile-orientation-deg")); 1608 1609var lat = getprop("/position/latitude-deg"); 1610var lon = getprop("/position/longitude-deg"); 1611 1612var res = local_weather.wind_interpolation(lat,lon,0.0); 1613 1614print("Wind: ", res[0], " tile alpha: ", getprop(lw~"tiles/tile[4]/orientation-deg")); 1615print("Mismatch: ", relangle(res[0], getprop(lw~"tiles/tile[4]/orientation-deg"))); 1616} 1617 1618 1619#print("===================="); 1620 1621settimer(watchdog_loop, 4.0); 1622} 1623 1624 1625 1626################### 1627# global variables 1628################### 1629 1630# these already exist in different namespace, but for ease of typing we define them here as well 1631 1632var lat_to_m = 110952.0; # latitude degrees to meters 1633var m_to_lat = 9.01290648208234e-06; # meters to latitude degrees 1634var ft_to_m = 0.30480; 1635var m_to_ft = 1.0/ft_to_m; 1636var inhg_to_hp = 33.76389; 1637var hp_to_inhg = 1.0/inhg_to_hp; 1638var lon_to_m = 0.0; #local_weather.lon_to_m; 1639var m_to_lon = 0.0; # local_weather.m_to_lon; 1640var lw = "/local-weather/"; 1641var cloud_shadow_flag = 0; 1642 1643var cloud_view_distance = getprop(lw~"config/clouds-visible-range-m"); 1644 1645var modelArrays = []; 1646var active_tile_list = []; 1647var thunderstormArray = []; 1648 1649# a bunch of variables to be updated per frame used by different 1650# routines, managed by the housekeeping loop 1651 1652var lwObserverLat = 0.0; 1653var lwObserverLon = 0.0; 1654var lwViewDir = 0.0; 1655var lwViewVec = []; 1656var lwTileIndex = 0; 1657 1658 1659##################################################### 1660# hashes to manage clouds in scenery or in the buffer 1661##################################################### 1662 1663 1664 1665var cloudBufferArray = []; 1666 1667 1668var cloudBuffer = { 1669 new: func(lat, lon, alt, path, orientation, index, type) { 1670 var c = { parents: [cloudBuffer] }; 1671 c.lat = lat; 1672 c.lon = lon; 1673 c.alt = alt; 1674 c.path = path; 1675 c.orientation = orientation; 1676 c.index = index; 1677 c.type = type; 1678 return c; 1679 }, 1680 get_distance: func { 1681 var pos = geo.aircraft_position(); 1682 var cpos = geo.Coord.new(); 1683 cpos.set_latlon(me.lat,me.lon,0.0); 1684 return pos.distance_to(cpos); 1685 }, 1686 get_course: func { 1687 var pos = geo.aircraft_position(); 1688 var cpos = geo.Coord.new(); 1689 cpos.set_latlon(me.lat,me.lon,0.0); 1690 return pos.course_to(cpos); 1691 }, 1692 show: func { 1693 print("lat: ",me.lat," lon: ",me.lon," alt: ",me.alt); 1694 print("path: ",me.path); 1695 1696 }, 1697 move: func { 1698 var windfield = weather_dynamics.get_windfield(me.index); 1699 var dt = weather_dynamics.time_lw - me.timestamp; 1700 me.lat = me.lat + windfield[1] * dt * local_weather.m_to_lat; 1701 me.lon = me.lon + windfield[0] * dt * local_weather.m_to_lon; 1702 me.timestamp = weather_dynamics.time_lw; 1703 }, 1704 1705}; 1706 1707 1708var cloudImpostorArray = []; 1709 1710var cloudImpostor = { 1711 new: func(modelNode) { 1712 var c = { parents: [cloudImpostor] }; 1713 c.modelNode = modelNode; 1714 return c; 1715 }, 1716 removeNodes: func { 1717 me.modelNode.remove(); 1718 }, 1719}; 1720 1721 1722var cloudShadowArray = []; 1723var cloudShadowCandidateArray = []; 1724var cloudShadowArraySize = 20; 1725var cloudShadowMaxDist = 100000.0; 1726var cloudShadowMinIndex = -1; 1727 1728var cloudShadow = { 1729 new: func (lat, lon, size, strength) { 1730 var s = {parents: [cloudShadow] }; 1731 s.lat = lat; 1732 s.lon = lon; 1733 s.size = size; 1734 s.strength = strength; 1735 s.shadow_flag = 0; 1736 return s; 1737 }, 1738}; 1739 1740var thunderstormHash = { 1741 new: func (lat, lon, alt, size, strength) { 1742 var t = {parents: [thunderstormHash] }; 1743 t.lat = lat; 1744 t.lon = lon; 1745 t.alt = alt; 1746 t.size = size; 1747 t.strength = strength; 1748 return t; 1749 }, 1750}; 1751 1752 1753var cloudSceneryArray = []; 1754var n_cloudSceneryArray = 0; 1755 1756var cloudScenery = { 1757 new: func(index, type, cloudNode, modelNode) { 1758 var c = { parents: [cloudScenery] }; 1759 c.index = index; 1760 c.type = type; 1761 c.cloudNode = cloudNode; 1762 c.modelNode = modelNode; 1763 c.calt = cloudNode.getNode("position/altitude-ft"); 1764 c.clat = cloudNode.getNode("position/latitude-deg"); 1765 c.clon = cloudNode.getNode("position/longitude-deg"); 1766 c.alt = c.calt.getValue(); 1767 c.lat = c.clat.getValue(); 1768 c.lon = c.clon.getValue(); 1769 return c; 1770 }, 1771 removeNodes: func { 1772 me.modelNode.remove(); 1773 me.cloudNode.remove(); 1774 }, 1775 to_buffer: func { 1776 var path = me.modelNode.getNode("path").getValue(); 1777 var orientation = me.cloudNode.getNode("orientation/true-heading-deg").getValue(); 1778 var b = cloudBuffer.new(me.lat, me.lon, me.alt, path, orientation, me.index, me.type); 1779 1780 if (local_weather.dynamics_flag == 1) 1781 { 1782 b.timestamp = me.timestamp; 1783 1784 if (me.type !=0) # Cumulus clouds get some extra info 1785 { 1786 b.flt = me.flt; 1787 b.rel_alt = me.rel_alt; 1788 b.evolution_timestamp = me.evolution_timestamp; 1789 } 1790 } 1791 1792 me.removeNodes(); 1793 return b; 1794 }, 1795 get_distance: func { 1796 var pos = geo.aircraft_position(); 1797 var cpos = geo.Coord.new(); 1798 var lat = me.clat.getValue(); 1799 var lon = me.clon.getValue(); 1800 cpos.set_latlon(lat,lon,0.0); 1801 return pos.distance_to(cpos); 1802 }, 1803 get_course: func { 1804 var pos = geo.aircraft_position(); 1805 var cpos = geo.Coord.new(); 1806 var lat = me.clat.getValue(); 1807 var lon = me.clon.getValue(); 1808 cpos.set_latlon(lat,lon,0.0); 1809 return pos.course_to(cpos); 1810 }, 1811 get_altitude: func { 1812 return me.calt.getValue(); 1813 }, 1814 correct_altitude: func { 1815 var lat = me.lat; 1816 var lon = me.lon; 1817 var convective_alt = weather_dynamics.tile_convective_altitude[me.index-1] + local_weather.alt_20_array[me.index-1]; 1818 var elevation = compat_layer.get_elevation(lat, lon); 1819 1820 if (local_weather.detailed_terrain_interaction_flag == 1) 1821 { 1822 var phi = local_weather.get_wind_direction(me.index) * math.pi/180.0; 1823 var grad = local_weather.get_terrain_gradient(lat, lon, elevation, phi, 1000.0); 1824 } 1825 else 1826 {var grad = 0.0;} 1827 1828 var alt_new = local_weather.get_convective_altitude(convective_alt, elevation, me.index, grad); 1829 me.target_alt = alt_new + me.rel_alt; 1830 }, 1831 correct_altitude_and_age: func { 1832 var lat = me.lat; 1833 var lon = me.lon; 1834 var convective_alt = weather_dynamics.tile_convective_altitude[me.index-1] + local_weather.alt_20_array[me.index-1]; 1835 1836 # get terrain elevation and landcover 1837 1838 var elevation = -1.0; var p_cover = 0.2;# defaults if there is no info 1839 var info = geodinfo(lat, lon); 1840 if (info != nil) 1841 { 1842 elevation = info[0] * local_weather.m_to_ft; 1843 if (info[1] != nil) 1844 { 1845 var landcover = info[1].names[0]; 1846 if (contains(local_weather.landcover_map,landcover)) {p_cover = local_weather.landcover_map[landcover];} 1847 else {p_cover = 0.2;} 1848 } 1849 } 1850 1851 if (local_weather.detailed_terrain_interaction_flag == 1) 1852 { 1853 var phi = local_weather.get_wind_direction(me.index) * math.pi/180.0; 1854 var grad = local_weather.get_terrain_gradient(lat, lon, elevation, phi, 1000.0); 1855 var lee_bias = local_weather.get_lee_bias(grad); 1856 } 1857 else 1858 { 1859 var grad = 0.0; 1860 var lee_bias = 1.0; 1861 } 1862 1863 # correct the altitude 1864 var alt_new = local_weather.get_convective_altitude(convective_alt, elevation, me.index, grad); 1865 me.target_alt = alt_new + me.rel_alt; 1866 1867 # correct fractional lifetime based on terrain below 1868 1869 var current_lifetime = math.sqrt(p_cover * lee_bias)/math.sqrt(0.35) * weather_dynamics.cloud_convective_lifetime_s; 1870 var fractional_increase = (weather_dynamics.time_lw - me.evolution_timestamp)/current_lifetime; 1871 me.flt = me.flt + fractional_increase; 1872 me.evolution_timestamp = weather_dynamics.time_lw; 1873 }, 1874 to_target_alt: func { 1875 if (me.type ==0) {return;} 1876 var alt_diff = me.target_alt - me.alt; 1877 if (alt_diff == 0.0) {return;} 1878 var max_vertical_movement_ft = weather_dynamics.dt_lw * weather_dynamics.cloud_max_vertical_speed_fts; 1879 if (abs(alt_diff) < max_vertical_movement_ft) 1880 { 1881 me.alt = me.target_alt; 1882 } 1883 else if (alt_diff < 0) 1884 { 1885 me.alt = me.alt -max_vertical_movement_ft; 1886 } 1887 else 1888 { 1889 me.alt = me.alt + max_vertical_movement_ft; 1890 } 1891 setprop(lw~"clouds/tile["~me.index~"]/cloud["~me.write_index~"]/position/altitude-ft", me.alt); 1892 }, 1893 move: func { 1894 1895 1896 var windfield = weather_dynamics.windfield; 1897 var dt = weather_dynamics.time_lw - me.timestamp; 1898 1899 me.lat = me.lat + windfield[1] * dt * local_weather.m_to_lat; 1900 me.lon = me.lon + windfield[0] * dt * local_weather.m_to_lon; 1901 1902 setprop(lw~"clouds/tile["~me.index~"]/cloud["~me.write_index~"]/position/latitude-deg", me.lat); 1903 setprop(lw~"clouds/tile["~me.index~"]/cloud["~me.write_index~"]/position/longitude-deg", me.lon); 1904 1905 me.timestamp = weather_dynamics.time_lw; 1906 1907 }, 1908 show: func { 1909 var lat = me.clat.getValue(); 1910 var lon = me.clon.getValue(); 1911 var alt = me.calt.getValue(); 1912 1913 var convective_alt = weather_dynamics.tile_convective_altitude[me.index-1] + local_weather.alt_20_array[me.index-1]; 1914 var elevation = compat_layer.get_elevation(lat, lon); 1915 print("lat :", lat, " lon: ", lon, " alt: ", alt); 1916 print("path: ", me.modelNode.getNode("path").getValue()); 1917 print("elevation: ", compat_layer.get_elevation(lat, lon), " cloudbase: ", convective_alt); 1918 if (me.type !=0) {print("relative: ", me.rel_alt, "target: ", me.target_alt);} 1919 }, 1920}; 1921 1922var cloudArray = []; 1923 1924var cloud = { 1925 new: func(type, subtype) { 1926 var c = { parents: [cloud] }; 1927 c.type = type; 1928 c.subtype = subtype; 1929 c.tracer_flag = 0; 1930 return c; 1931 }, 1932 remove: func { 1933 var p = props.Node.new({ "layer" : 0, 1934 "index": me.cloud_index }); 1935 fgcommand("del-cloud", p); 1936 }, 1937 move: func { 1938 # this doesn't move a cloud in the scenery, but updates its position in internal space 1939 var windfield = local_weather.windfield; 1940 var dt = local_weather.time_lw - me.timestamp; 1941 1942 me.lat = me.lat + windfield[1] * dt * local_weather.m_to_lat; 1943 me.lon = me.lon + windfield[0] * dt * local_weather.m_to_lon; 1944 me.timestamp = weather_dynamics.time_lw; 1945 1946 }, 1947 correct_altitude: func { 1948 var convective_alt = weather_dynamics.tile_convective_altitude[me.index-1] + local_weather.alt_20_array[me.index-1]; 1949 var elevation = compat_layer.get_elevation(me.lat, me.lon); 1950 1951 if (local_weather.detailed_terrain_interaction_flag == 1) 1952 { 1953 var phi = local_weather.get_wind_direction(me.index) * math.pi/180.0; 1954 var grad = local_weather.get_terrain_gradient(me.lat, me.lon, elevation, phi, 1000.0); 1955 } 1956 else 1957 {var grad = 0.0;} 1958 1959 var alt_new = local_weather.get_convective_altitude(convective_alt, elevation, me.index, grad); 1960 var target_alt = alt_new + me.rel_alt; 1961 1962 var p = props.Node.new({ "layer" : 0, 1963 "index": me.cloud_index, 1964 "lat-deg": me.lat, 1965 "lon-deg": me.lon, 1966 "alt-ft": target_alt 1967 }); 1968 fgcommand("move-cloud",p); 1969 1970 me.alt = target_alt; 1971 }, 1972}; 1973 1974 1975################### 1976# helper functions 1977################### 1978 1979var calc_geo = func(clat) { 1980 1981lon_to_m = math.cos(clat*math.pi/180.0) * lat_to_m; 1982m_to_lon = 1.0/lon_to_m; 1983} 1984 1985var get_lat = func (x,y,phi) { 1986 1987return (y * math.cos(phi) - x * math.sin(phi)) * m_to_lat; 1988} 1989 1990var get_lon = func (x,y,phi) { 1991 1992return (x * math.cos(phi) + y * math.sin(phi)) * m_to_lon; 1993} 1994 1995 1996var relangle = func (alpha, beta) { 1997 1998var angdiff = abs (alpha - beta); 1999 2000if ((360.0 - angdiff) < angdiff) 2001 {angdiff = 360.0 - angdiff}; 2002 2003return angdiff; 2004} 2005 2006 2007var norm_relangle = func (alpha, beta) { 2008 2009var angdiff = beta - alpha; 2010 2011while (angdiff < 0.0) 2012 {angdiff = angdiff + 360.0;} 2013 2014while (angdiff > 360.0) 2015 {angdiff = angdiff - 360.0;} 2016 2017if (angdiff == 360.0) 2018 {angdiff = 0.0;} 2019 2020return angdiff; 2021} 2022 2023 2024var delete_from_vector = func(vec, index) { 2025 2026var n = index+1; 2027 2028var vec_end = subvec(vec, n); 2029 2030setsize(vec, n-1); 2031return vec~vec_end; 2032 2033} 2034