Bugfix to globals.fgcommand().
New Node.setValues() method which sets whole property trees from Nasal data. A view.nas module, which takes over handling of the X/x zoom keys. It clamps the FOV to a dynamically calculated maximum corresponding to typical human visual accuity, and pops up a pretty dialog informing you of the new FOV.
This commit is contained in:
parent
14ad64676d
commit
3677b96fad
4 changed files with 162 additions and 30 deletions
|
@ -20,8 +20,8 @@ isa = func {
|
|||
# tree.
|
||||
#
|
||||
fgcommand = func {
|
||||
if(isa(arg[1], props.Node)) { _fgcommand(arg[0], arg[1]._g) }
|
||||
_fgcommand(arg[0], propTree);
|
||||
if(isa(arg[1], props.Node)) { arg[1] = arg[1]._g }
|
||||
_fgcommand(arg[0], arg[1]);
|
||||
}
|
||||
|
||||
##
|
||||
|
|
|
@ -30,14 +30,47 @@ Node = {
|
|||
|
||||
};
|
||||
|
||||
# Static constructor. Accepts a hash as an argument and duplicates
|
||||
# its contents in the property node. ex:
|
||||
# Node.new({ value : 1.0, units : "ms" });
|
||||
##
|
||||
# Static constructor for a Node object. Accepts a Nasal hash
|
||||
# expression to initialize the object a-la setValues().
|
||||
#
|
||||
Node.new = func {
|
||||
result = wrapNode(_new());
|
||||
if(typeof(arg[0]) == "hash") {
|
||||
foreach(k; keys(arg[0])) {
|
||||
result.getNode(k, 1).setValue(arg[0][k]);
|
||||
if(size(arg) >= 0 and typeof(arg[0]) == "hash") {
|
||||
result.setValues(arg[0]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
##
|
||||
# Useful utility. Sets a whole property tree from a Nasal hash
|
||||
# object, such that scalars become leafs in the property tree, hashes
|
||||
# become named subnodes, and vectors become indexed subnodes. This
|
||||
# works recursively, so you can define whole property trees with
|
||||
# syntax like:
|
||||
#
|
||||
# dialog = {
|
||||
# name : "exit", width : 180, height : 100, modal : 0,
|
||||
# text : { x : 10, y : 70, label : "Hello World!" } };
|
||||
#
|
||||
Node.setValues = func {
|
||||
foreach(k; keys(arg[0])) { me._setChildren(k, arg[0][k]); }
|
||||
}
|
||||
|
||||
##
|
||||
# Private function to do the work of setValues().
|
||||
# The first argument is a child name, the second a nasal scalar,
|
||||
# vector, or hash.
|
||||
#
|
||||
Node._setChildren = func {
|
||||
name = arg[0]; val = arg[1];
|
||||
subnode = me.getNode(name, 1);
|
||||
if(typeof(val) == "scalar") { subnode.setValue(val); }
|
||||
elsif(typeof(val) == "hash") { subnode.setValues(val); }
|
||||
elsif(typeof(val) == "vector") {
|
||||
for(i=0; i<size(val); i=i+1) {
|
||||
iname = name ~ "[" ~ i ~ "]";
|
||||
me._setChildren(iname, val[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
117
Nasal/view.nas
Normal file
117
Nasal/view.nas
Normal file
|
@ -0,0 +1,117 @@
|
|||
##
|
||||
## view.nas
|
||||
##
|
||||
## Nasal code for implementing view-specific functionality. Right
|
||||
## now, it does intelligent FOV scaling in the view.increase() and
|
||||
## view.decrease() handlers.
|
||||
##
|
||||
|
||||
#
|
||||
# This is a neat trick. We want these globals to be initialized at
|
||||
# startup, but there is no guarantee that the props.nas module will be
|
||||
# loaded yet when we are run. So set the values to nil at startup (so
|
||||
# that there is a value in the lexical environment -- otherwise
|
||||
# assigning them in INIT() will only make local variables),
|
||||
# and then assign them from inside a timer that we set to run
|
||||
# immediately *after* startup.
|
||||
#
|
||||
# Nifty hacks notwithstanding, this really isn't the right way to do
|
||||
# this. There ought to be an "import" mechanism we can use to resolve
|
||||
# dependencies between modules.
|
||||
#
|
||||
fovProp = nil;
|
||||
screenProp = nil;
|
||||
popupNode = nil;
|
||||
labelNode = nil;
|
||||
fovDialog = nil;
|
||||
INIT = func {
|
||||
print("view.INIT()");
|
||||
|
||||
fovProp = props.globals.getNode("/sim/current-view/field-of-view");
|
||||
screenProp = props.globals.getNode("/sim/startup/xsize");
|
||||
|
||||
# Set up the dialog property node:
|
||||
tmpl = { name : "fov", modal : 0, width : "120", height : "40",
|
||||
text : { x : 10, y : 6, label : "FOV:" } };
|
||||
popupNode = props.Node.new(tmpl);
|
||||
text = popupNode.getNode("text", 1);
|
||||
labelNode = popupNode.getNode("text/label");
|
||||
|
||||
fgcommand("dialog-new", popupNode);
|
||||
|
||||
# Cache the FGCommand argument
|
||||
fovDialog = props.Node.new({ "dialog-name" : "fov" });
|
||||
}
|
||||
settimer(INIT, 0);
|
||||
|
||||
# Dynamically calculate limits so that it takes STEPS iterations to
|
||||
# traverse the whole range, the maximum FOV is fixed at 120 degrees,
|
||||
# and the minimum corresponds to normal maximum human visual acuity
|
||||
# (~1 arc minute of resolution, although apparently people vary widely
|
||||
# in this ability). Quick derivation of the math:
|
||||
#
|
||||
# mul^steps = max/min
|
||||
# steps * ln(mul) = ln(max/min)
|
||||
# mul = exp(ln(max/min) / steps)
|
||||
STEPS = 40;
|
||||
ACUITY = 1/60; # Maximum angle subtended by one pixel (== 1 arc minute)
|
||||
max = min = mul = 0;
|
||||
|
||||
calcMul = func {
|
||||
max = 120; # Fixed at 120 degrees
|
||||
min = screenProp.getValue() * ACUITY;
|
||||
mul = math.exp(math.ln(max/min) / STEPS);
|
||||
}
|
||||
|
||||
##
|
||||
# Hackish round-to-one-decimal-place function. Nasal needs a
|
||||
# sprintf() interface, or something similar...
|
||||
#
|
||||
format = func {
|
||||
val = int(arg[0]);
|
||||
frac = int(10 * (arg[0] - val) + 0.5);
|
||||
return val ~ "." ~ substr("" ~ frac, 0, 1);
|
||||
}
|
||||
|
||||
##
|
||||
# Handler. Increase FOV by one step
|
||||
#
|
||||
increase = func {
|
||||
calcMul();
|
||||
val = fovProp.getValue() * mul;
|
||||
if(val == max) { return; }
|
||||
if(val > max) { val = max }
|
||||
fovProp.setDoubleValue(val);
|
||||
popup(val);
|
||||
}
|
||||
|
||||
##
|
||||
# Handler. Decrease FOV by one step
|
||||
#
|
||||
decrease = func {
|
||||
calcMul();
|
||||
val = fovProp.getValue() / mul;
|
||||
if(val == min) { return; }
|
||||
if(val < min) { val = min }
|
||||
fovProp.setDoubleValue(val);
|
||||
popup(val);
|
||||
}
|
||||
|
||||
##
|
||||
# Pop up the "fov" dialog for a moment.
|
||||
#
|
||||
popdown = func { fgcommand("dialog-close", fovDialog); }
|
||||
popup = func {
|
||||
fov = arg[0];
|
||||
fov = format(fov);
|
||||
|
||||
labelNode.setValue("FOV: " ~ fov);
|
||||
|
||||
# Create it, show it
|
||||
popdown();
|
||||
fgcommand("dialog-show", fovDialog);
|
||||
|
||||
# And kill it automatically after a second
|
||||
settimer(popdown, 1);
|
||||
}
|
||||
|
26
keyboard.xml
26
keyboard.xml
|
@ -557,22 +557,6 @@ calculated by adding 256 to the GLUT key value in glut.h.
|
|||
</binding>
|
||||
</key>
|
||||
|
||||
<key n="67">
|
||||
<name>C</name>
|
||||
<desc>scripting test</desc>
|
||||
<binding>
|
||||
<command>script</command>
|
||||
<script>
|
||||
int main ()
|
||||
{
|
||||
print("Longitude: ", get_property("/position/longitude-deg"), " deg\n");
|
||||
print("Latitude: ", get_property("/position/latitude-deg"), " deg\n");
|
||||
print("Altitude: ", get_property("/position/altitude-ft"), " ft\n");
|
||||
}
|
||||
</script>
|
||||
</binding>
|
||||
</key>
|
||||
|
||||
<key n="71">
|
||||
<name>G</name>
|
||||
<desc>Gear down.</desc>
|
||||
|
@ -638,9 +622,8 @@ calculated by adding 256 to the GLUT key value in glut.h.
|
|||
<name>X</name>
|
||||
<desc>Increase field of view.</desc>
|
||||
<binding>
|
||||
<command>property-multiply</command>
|
||||
<property>/sim/current-view/field-of-view</property>
|
||||
<factor type="double">1.05</factor>
|
||||
<command>nasal</command>
|
||||
<script>view.increase()</script>
|
||||
</binding>
|
||||
</key>
|
||||
|
||||
|
@ -885,9 +868,8 @@ calculated by adding 256 to the GLUT key value in glut.h.
|
|||
<name>x</name>
|
||||
<desc>Decrease field of view.</desc>
|
||||
<binding>
|
||||
<command>property-multiply</command>
|
||||
<property>/sim/current-view/field-of-view</property>
|
||||
<factor type="double">0.952380952380</factor>
|
||||
<command>nasal</command>
|
||||
<script>view.decrease()</script>
|
||||
</binding>
|
||||
</key>
|
||||
|
||||
|
|
Loading…
Reference in a new issue