New Nasal code, with lots of input binding handlers in controls.nas.
[fg:toms-fgdata.git] / Nasal / view.nas
1 ##
2 ## view.nas
3 ##
4 ##  Nasal code for implementing view-specific functionality.  Right
5 ##  now, it does intelligent FOV scaling in the view.increase() and
6 ##  view.decrease() handlers.
7 ##
8
9 #
10 # This is a neat trick.  We want these globals to be initialized at
11 # startup, but there is no guarantee that the props.nas module will be
12 # loaded yet when we are run.  So set the values to nil at startup (so
13 # that there is a value in the lexical environment -- otherwise
14 # assigning them in INIT() will only make local variables),
15 # and then assign them from inside a timer that we set to run
16 # immediately *after* startup.
17 #
18 # Nifty hacks notwithstanding, this really isn't the right way to do
19 # this.  There ought to be an "import" mechanism we can use to resolve
20 # dependencies between modules.
21 #
22 fovProp = nil;
23 INIT = func {
24     fovProp = props.globals.getNode("/sim/current-view/field-of-view");
25
26 }
27 settimer(INIT, 0);
28
29 # Dynamically calculate limits so that it takes STEPS iterations to
30 # traverse the whole range, the maximum FOV is fixed at 120 degrees,
31 # and the minimum corresponds to normal maximum human visual acuity
32 # (~1 arc minute of resolution, although apparently people vary widely
33 # in this ability).  Quick derivation of the math:
34 #
35 #   mul^steps = max/min
36 #   steps * ln(mul) = ln(max/min)
37 #   mul = exp(ln(max/min) / steps)
38 STEPS = 40;
39 ACUITY = 1/60; # Maximum angle subtended by one pixel (== 1 arc minute)
40 max = min = mul = 0;
41 calcMul = func {
42     max = 120; # Fixed at 120 degrees
43     min = getprop("/sim/startup/xsize") * ACUITY;
44     mul = math.exp(math.ln(max/min) / STEPS);
45 }
46
47 ##
48 # Hackish round-to-one-decimal-place function.  Nasal needs a
49 # sprintf() interface, or something similar...
50 #
51 format = func {
52     val = int(arg[0]);
53     frac = int(10 * (arg[0] - val) + 0.5);
54     return val ~ "." ~ substr("" ~ frac, 0, 1);
55 }
56
57 ##
58 # Handler.  Increase FOV by one step
59 #
60 increase = func {
61     calcMul();
62     val = fovProp.getValue() * mul;
63     if(val == max) { return; }
64     if(val > max) { val = max }
65     fovProp.setDoubleValue(val);
66     gui.popupTip("FOV: " ~ format(val));
67 }
68
69 ##
70 # Handler.  Decrease FOV by one step
71 #
72 decrease = func {
73     calcMul();
74     val = fovProp.getValue() / mul;
75     msg = "FOV: " ~ format(val);
76     if(val < min) { msg = msg ~ " (overzoom)"; }
77     fovProp.setDoubleValue(val);
78     gui.popupTip(msg);
79 }
80
81 ##
82 # Handler.  Reset view to default.
83 #
84 resetView = func {
85     setprop("/sim/current-view/goal-heading-offset-deg",
86             getprop("/sim/current-view/config/heading-offset-deg"));
87     setprop("/sim/current-view/goal-pitch-offset-deg",
88             getprop("/sim/current-view/config/pitch-offset-deg"));
89     setprop("/sim/current-view/field-of-view",
90             getprop("/sim/current-view/config/field-of-view-deg"))
91 }
92
93 ##
94 # Handler.  Step to the next view.
95 #
96 stepView = func {
97     curr = getprop("/sim/current-view/view-number");
98     views = props.globals.getNode("/sim").getChildren("view");
99     curr = curr + arg[0];
100     if   (curr < 0)            { curr = size(views) - 1; }
101     elsif(curr >= size(views)) { curr = 0; }
102     setprop("/sim/current-view/view-number", curr);
103
104     # And pop up a nice reminder
105     gui.popupTip(views[curr].getNode("name").getValue());
106 }
107
108 ##
109 # Standard view "slew" rate, in degrees/sec.
110
111 VIEW_PAN_RATE = 60;
112
113 ##
114 # Pans the view horizontally.  The argument specifies a relative rate
115 # (or number of "steps" -- same thing) to the standard rate.
116 #
117 panViewDir = func {
118     controls.slewProp("/sim/current-view/goal-heading-offset-deg",
119                       arg[0] * VIEW_PAN_RATE);
120 }
121
122 ##
123 # Pans the view vertically.  The argument specifies a relative rate
124 # (or number of "steps" -- same thing) to the standard rate.
125 #
126 panViewPitch = func {
127     controls.slewProp("/sim/current-view/goal-pitch-offset-deg",
128                       arg[0] * VIEW_PAN_RATE);
129 }