Phi: nicer scroll animation for METAR widget
[fg:fgdata.git] / Nasal / glide_slope_tunnel.nas
1 # Draw 3 degree glide slope tunnel for the nearest airport's most suitable runway
2 # considering wind direction and runway size.
3 # Activate with  --prop:sim/rendering/glide-slope-tunnel=1 or via Help menu
4
5 var MARKER = "Models/Geometry/square.xml";      # tunnel marker
6 var DIST = 1000;                                # distance between markers
7 var NUM = 30;                                   # number of tunnel markers
8 var ANGLE = 3 * math.pi / 180;                  # glide slope angle in radian
9 var HOFFSET = 274;                              # distance between begin of runway and touchdown area (900 ft)
10 var INTERVAL = 5;                               # check for nearest airport
11
12 var voffset = DIST * math.sin(ANGLE) / math.cos(ANGLE);
13 var apt = nil;
14 var tunnel = [];
15 setsize(tunnel, NUM);
16
17
18 var normdeg = func(a) {
19         while (a >= 180)
20                 a -= 360;
21         while (a < -180)
22                 a += 360;
23         return a;
24 }
25
26
27 # Find best runway for current wind direction (or 270), also considering length and width.
28 #
29 var best_runway = func(apt) {
30         var wind_speed = getprop("/environment/wind-speed-kt");
31         var wind_from = wind_speed ? getprop("/environment/wind-from-heading-deg") : 270;
32         var max = -1;
33         var rwy = nil;
34
35         foreach (var r; keys(apt.runways)) {
36                 var curr = apt.runways[r];
37                 var deviation = math.abs(normdeg(wind_from - curr.heading)) + 1e-20;
38                 var v = (0.01 * curr.length + 0.01 * curr.width) / deviation;
39
40                 if (v > max) {
41                         max = v;
42                         rwy = curr;
43                 }
44         }
45         return rwy;
46 }
47
48
49 # Draw 3 degree glide slope tunnel.
50 #
51 var draw_tunnel = func(rwy) {
52         var m = geo.Coord.new().set_latlon(rwy.lat, rwy.lon);
53         m.apply_course_distance(rwy.heading + 180, rwy.length / 2 - rwy.threshold - HOFFSET);
54
55         var g = geodinfo(m.lat(), m.lon());
56         var elev = g != nil ? g[0] : apt.elevation;
57         forindex (var i; tunnel) {
58                 if (tunnel[i] != nil)
59                         tunnel[i].remove();
60
61                 m.set_alt(elev);
62                 tunnel[i] = geo.put_model(MARKER, m, rwy.heading);
63                 m.apply_course_distance(rwy.heading + 180, DIST);
64                 elev += voffset;
65         }
66 }
67
68
69 var loop = func(id) {
70         id == loopid or return;
71         var a = airportinfo();
72         if (apt == nil or apt.id != a.id) {
73                 apt = a;
74                 var is_heliport = 1;
75                 foreach (var rwy; keys(apt.runways))
76                         if (rwy[0] != `H`)
77                                 is_heliport = 0;
78
79                 if (!is_heliport) {
80                         draw_tunnel(best_runway(apt));
81                         gui.popupTip(apt.id ~ " - \"" ~ apt.name ~ "\"", 6);
82                 }
83         }
84         settimer(func loop(id), INTERVAL);
85 }
86
87
88 var loopid = 0;
89
90 var fdm_init_listener = _setlistener("/sim/signals/fdm-initialized", func {
91         removelistener(fdm_init_listener); # uninstall, so we're only called once
92         # remove top bar unless otherwise specified
93         var top = props.globals.initNode("/sim/model/geometry/square/top", 1, "BOOL");
94
95         setlistener("/sim/rendering/glide-slope-tunnel", func(n) {
96                 loopid += 1;
97                 if (n.getValue()) {
98                         apt = nil;
99                         return loop(loopid);
100                 }
101
102                 forindex (var i; tunnel) {
103                         if (tunnel[i] != nil) {
104                                 tunnel[i].remove();
105                                 tunnel[i] = nil;
106                         }
107                 }
108         }, 1);
109 });
110
111