##
## 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;
INIT = func {
    fovProp = props.globals.getNode("/sim/current-view/field-of-view");

}
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 = getprop("/sim/startup/xsize") * 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);
    gui.popupTip("FOV: " ~ format(val));
}

##
# Handler.  Decrease FOV by one step
#
decrease = func {
    calcMul();
    val = fovProp.getValue() / mul;
    msg = "FOV: " ~ format(val);
    if(val < min) { msg = msg ~ " (overzoom)"; }
    fovProp.setDoubleValue(val);
    gui.popupTip(msg);
}

##
# Handler.  Reset FOV to default.
#
resetFOV = func {
    setprop("/sim/current-view/field-of-view",
            getprop("/sim/current-view/config/default-field-of-view-deg"));
}

##
# Handler.  Reset view to default.
#
resetView = func {
    setprop("/sim/current-view/goal-heading-offset-deg",
            getprop("/sim/current-view/config/heading-offset-deg"));
    setprop("/sim/current-view/goal-pitch-offset-deg",
            getprop("/sim/current-view/config/pitch-offset-deg"));
    setprop("/sim/current-view/goal-roll-offset-deg",
            getprop("/sim/current-view/config/roll-offset-deg"));
    resetFOV();
}

##
# Handler.  Step to the next view.
#
stepView = func {
    curr = getprop("/sim/current-view/view-number");
    views = props.globals.getNode("/sim").getChildren("view");
    curr = curr + arg[0];
    if   (curr < 0)            { curr = size(views) - 1; }
    elsif(curr >= size(views)) { curr = 0; }
    setprop("/sim/current-view/view-number", curr);

    # And pop up a nice reminder
    gui.popupTip(views[curr].getNode("name").getValue());
}

##
# Standard view "slew" rate, in degrees/sec.
# 
VIEW_PAN_RATE = 60;

##
# Pans the view horizontally.  The argument specifies a relative rate
# (or number of "steps" -- same thing) to the standard rate.
#
panViewDir = func {
    controls.slewProp("/sim/current-view/goal-heading-offset-deg",
                      arg[0] * VIEW_PAN_RATE);
}

##
# Pans the view vertically.  The argument specifies a relative rate
# (or number of "steps" -- same thing) to the standard rate.
#
panViewPitch = func {
    controls.slewProp("/sim/current-view/goal-pitch-offset-deg",
                      arg[0] * VIEW_PAN_RATE);
}