--======================================================================================================================
--                                      LINA = "LINear  Animation"

--  This Lua snippet creates linear animation Lua objects.  The method employed in this script is not the most memory
--  efficient; however, it is easy enough to comprehend and use.  A more advanced and memory optimized version of this 
--  script, using Lua meta-tables is also provided in this example section. 
--
--  There are two primary properties of these animation objects, 1) an 'instant value' property,  .instv  and 2) a 
--  "target" property,  .target.  The .instv 'animates to' the target value whenever the values don't match 
--  (unless .paused = true).  To initiate the animation, you simply set the  target property and the .instv will 
--  animate TO the target value, regardless of whether its up or down.  

--  When creating animation objects, there are multiple ways to define the animation, depending on how many arguments
--  you supply to the constructor function.  Sometimes you don't know the rate, but DO know how long something takes
--  TO animate between limits etc:

--  • With one argument, you specify the rate of the animation, in units/second.  There is no hi/lo limit values.
--  • With two arguments, you specify the "UP" rate and the "DOWN" rate.  There is no hi/lo limit values
--  • With three arguments, you specify the lo limit, the high limit, and the rate of animation.

--  The three methods above are only for convenience.  You may still set any parameters affecting the animation limits
--  and rates via provided the method functions.

--  There are  multiple'status properties' that you can query whether the animation is running or not. 

--  *   A .direction property( +1/-1) tells you if the .instv (animation) is increasing or decreasing. (0 if stopped)
--  *   A .inTransit property (0/1) tells you if the animation is actively in progress.
--  *   A .paused property (true/false) to tell you if the animation is paused.

--  The following methods are provided to control the animation and change its parameters.  These may be changed while
--  the object is actively animating as well:

--  *   .setLowLimit(l_low_limit)
--  *   .setHiLimit(l_hi_limit)
--  *   .setDnRate(l_dn_rate)
--  *   .setUpRate(l_up_rate)
--  *   .setTarget(l_target)
--  *   .sync(sync_value)           --  sets the .target and .instv values to the sync_value, stopping the animation.
--  *   .setRate(l_rate)            --  sets the animation rate for both UP and DOWN directions.
--  *   .setPause(l_pause_state)

--  COMMON USE CASES
--  *   Animate a cockpit switch.
--  *   Animate a light extending from a wing.
--  *   Ramp up some internal variable, like the RPM of a gyro and tie it to an FMOD sound.
--  *   Drive a set of altitude 'scrollwheels' that are motorized at some max rate (and can't keep up in a dive)
--  *   Animate anything driven by an electric motor that has a fixed rate.
--  *   Animate a flap lever between detents when using commands like:  "up a notch" or "down a notch"

--======================================================================================================================
--  2)  Global Var Declarations

lina_objects        =   {}                                  --  global table to hold all the lina objects
------------------------------------------------------------------------------------------------------------------------
--  4)  X-Plane Datarefs

xdr_sim_paused      =   find_dataref("sim/time/paused")     --  we don't want to animate when the sim is paused
------------------------------------------------------------------------------------------------------------------------                
--  9)  Lua Object Constructors.  Use to create new lina objects

function    new_lina(...)

    local   args    =   {...}
    local   o       =   {}

    --  user set properties
    o.uni_rate      =   0           --  animation rate is the same going up or down
    o.up_rate       =   0           --  animation rate when target is higher than the instv
    o.dn_rate       =   0           --  animation rate when the target is lower than the instv
    o.lo_limit      =   0           --  lower limit of the animation values (commonly 0)
    o.hi_limit      =   1           --  upper limit of the animation values (commonly 1)
    o.duration      =   duration    --  length of animation in seconds
    o.range         =   range       --  range of value.  (range/duration = rate)
    o.target        =   0.0

    --  calculated properties
    o.instv         =   0.0
    o.d_error       =   0.0         --  delta error
    o.d_second      =   0.0         --  delta value, per second
    o.inTransit     =   false
    o.direction     =   0
    o.last_target   =   0
    o.paused        =   false

------------------------------------------------------------------------------------------------------------------------ 
--                      3 Different ways to specify the animation rate during declarations
------------------------------------------------------------------------------------------------------------------------
    if      #args   =   1   then    --  (universal_rate, i.e.  x units/second)
        o.up_rate   =   args[1]
        o.udn_rate  =   args[1]

    elseif  #args   ==  2   then    --  (up_rate,  down_rate)  animates from instv to target at rates specified
        o.up_rate   =   args[1]
        o.dn_rate   =   args[2]

    elseif  #args   ==  3   then    --  (lo_limit,  hi_limit,   duration)  assumes uni_rate
        o.lo_limit  =   args[1]
        o.hi_limit  =   args[2]
        o.duration  =   args[3]
        o.uni_rate  =   (o.hi_limit - o.lo_limit) / o.duration      
    end

------------------------------------------------------------------------------------------------------------------------ 
--                                              "Object" METHODS
------------------------------------------------------------------------------------------------------------------------
    o.setLowLimit       =   function(l_low_limit)   --  Sets the Lower Limit.  Anim won't go past this valuer   
        o.lo_limit  =   l_lo_limit
    end

    o.setHiLimit        =   function(l_hi_limit)    --  Sets the Upper Limit.  ....
        o.hi_limit  =   l_hi_limit
    end

    o.setTarget     =   function(l_target)          --  Sets the target value to animate to.
        o.target    =   l_target
    end

    o.sync          =   function(sync_value)        --  Syncs .target and .instv to same value, ceasing the animation.
         o.target   =   sync_value
         o.instv    =   sync_value
         o.d_error  =   0
    end 

    o.setRate       =   function(l_rate)            --  Sets the rate (units/secons) of the animation

    end

end             
------------------------------------------------------------------------------------------------------------------------
--  10) Lua Object Instantiations

test_lina   =   new_lina(1.2)
------------------------------------------------------------------------------------------------------------------------
--  11) Utility Function Definitions

function    lina_flcb()

    if  xdr_sim_paused == 0 then    --  sim is running
        for i, v in pairs(lina_objects) do

            v.d_error   =   v.target - v.instv      --  calculate current error

            if  math.abs(v.d_error) <= v.d_second * SIM_PERIOD  then    --  reached target.  set target = instv
                v.instv         =   v.target
                v.direction     =   0
                v.inTransit     =   false
                v.d_error       =   0               
            else    -- not reached target, we need to animate the values

                v.inTransit     =   true

                if  v.d_error   <   0   then    --  instv is higher than target.  animate down
                    v.direction     =   -1              

                    v.instv =   v.instv - v.d_second * SIM_PERIOD

                else    --  instv is lower than target, animate up.
                    v.direction     =   1

                    v.instv =   v.instv + v.d_second * SIM_PERIOD
                end

                v.last_target   =   v.target                
            end
        end
    end
end
------------------------------------------------------------------------------------------------------------------------
--  13) X-Plane Major Callbacks

function    after_physics()
    lina_flcb()
end

--======================================================================================================================
--                                              EXAMPLE USAGE
--======================================================================================================================

--  we are going to drive a custom dataref with our animation object.  So we need to create it
cdr_animated_switch     =   create_dataref("example/animation/animated_switch",     "number")

--  This custom dataref will read '1' whenever the animation is in transit and 0 when its not.  We're just putting
--  it here to show how you can test against the .inTransit value to know if an animation is ongoing or not.
cdr_inTransit           =   create_dataref("example/animation/inTransit",           "number")

--  create a linear animation object, clamped between the range 0 and 1 and it takes 5s to cover that range.
lina_switch_animation   =   new_lina(0, 1, 5)

--  set the .target to 1 during the the initial script load, it will start animating immediately because .instv = 0
lina_switch_animation.setTarget(1)  

--  'after_physics' is one of XLua's default flight loop functions that run continuously.
function    after_physics()

    --  when the scripts loaded, we set the .target to 1 above, so it will take five seconds before the animation
    --  .instv property reaches the value of 1.  The block below runs continuously and "monitors" the action.  Once
    --  the .instv property reaches 1 (after 5 seconds), then this block will execute and set the target back to 0 and 
    --  the animation will immediately reverse and go back down to zero.

    if  lina_switch_animation.instv == 1    then
        lina_switch_animation.setTarget(0)
    end

    --  this block monitors the animation status and when the animation is actively in progress, sets the custom
    --  dataref to a value of 1.  When the animation stops (once .instv = .target) then this DR reads 0.
    if  lina_switch_animation.inTransit then
        cdr_inTransit   =   1
    else
        cdr_inTransit   =   0
    end

    --  every flight loop, we update the custom datarefs with the relevant object values 
    cdr_animated_switch =   lina_switch_animation.instv
end