Nathan Lamont

Notes to Self

Linear Interpolation (lerp)

From http://howlingmoonsoftware.com/wordpress/827-2/

It’s super useful, and can be used for all sorts of things. With a higher constant, it’s a good way to smooth out jittery input from a mouse or joystick. With a low constant, it’s a nice way to smoothly animate a progress bar or a camera following a player. It will never overshoot the target value even if it’s changing, and it changes the speed based on how far away it is so it will always quickly converge on the target. Pretty good for a one liner!

He states the standard use as:

value = lerp(value, targetValue, 0.1);

And then the improved version as:

Let’s replace the simple lerp constant with an exponential function that involves time, and see how it works.

// exp() works just as well, probably with little to no measurable performance difference.
value = lerp(target, value, exp2(-rate*deltaTime))

In this version, rate controls how quickly the value converges on the target. With a rate of 1.0, the value will move halfway to the target each second. If you double a rate, the value will move in twice as fast. If you halve the rate, it will move in half as fast. Couldn’t be easier.

Even better, it’s framerate independent. If you lerp this way 60 times with a delta time of 1/60 s, it will be the same result as lerping 30 times with 1/30 s, or once with 1 s. No fixed time step required nor the jittery movement it causes. However, do keep in mind that if your target value is changing over time (such as one object following another) you won’t get the exact same behavior. It’s close enough for many uses though.

https://github.com/dclelland/Lerp/blob/master/Lerp.swift

But also recall easing functions from YUI as implemented in Lua for word game.

import('gamecore/timer/timer')
simpleClass = require 'loop.simple'

t_constants = {
    types = {
        ease_in_and_out,
        ease_in,
        ease_out,
        linear,
    }
}
game.transition = simpleClass.class{
    type = t_constants.types.ease_in_and_out,
    length = 1.0,
    pause = false,
}

game.transition.constants = t_constants
t_constants = nil

function game.transition:__init( object)
    if object.from == nil then error("Transition expected 'from' values") end
    if object.to == nil then error("Transition expected 'to' values") end
    object.easing = object.easing or game.transition.easing.easeBoth
    object.bounce = object.bounce or false
    
    local result = simpleClass.rawnew( self, object)
    result.current = {}
    for k, v in pairs( result.from) do
        result.current[k] = result.from[k]
    end
    result.start_time = getTime()
    result.now_time = result.start_time
    result.end_time = result.start_time + result.length
    result.percent_complete = 0 -- from 0 to 1.0
    result.raw_percent_complete = 0
    return result
end

function game.transition:update( timeToAdd, pause, forceComplete)
    if not self.done then
        if not pause or not self.pause then
            self.now_time = self.now_time + timeToAdd
            if forceComplete then
                self.now_time = self.end_time
            end
        end
        if self.now_time >= self.end_time then
            self.done = true
            self.raw_percent_complete = 1.0
            --self.percent_complete = 1.0
            self.percent_complete = self.easing( self.raw_percent_complete, 0, 1, 1)
            
            if self.final == nil then self.final = self.to end
            for k, v in pairs( self.from) do
                self.current[k] = self.final[k]
            end
        else
            self.raw_percent_complete = self.now_time - self.start_time
            self.raw_percent_complete = self.raw_percent_complete / self.length
            self.percent_complete = self.raw_percent_complete
            self.percent_complete = self.easing( self.raw_percent_complete, 0, 1, 1)
            for k, v in pairs( self.from) do
                self.current[k] = self.from[k] + ((self.to[k] - self.from[k]) * self.percent_complete)
            end
        end
        if self.updateFunction ~= nil then
            self.updateFunction( self.object, self)
        end
        if self.done and self.completeFunction ~= nil then
            self.completeFunction( self.object, self)
        end
    end
end

function game.transition.add( object)
    local newTransition = game.transition( object)
    table.insert( game.timer.current, newTransition)
    newTransition:update( 0)
end


game.transition.easing = {}
game.transition.easing.easeBoth = function( t, b, c, d)
    local result
    local tt = t / (d / 2)
    if tt < 1 then
        result = c/2*tt*tt+b
    else
        t = tt - 1
        result = -c/2*(t*(t-2)-1) + b
    end
    return result
end

game.transition.easing.easeIn = function( t, b, c, d)
    local tt = t / d
    return c * (tt) * (tt) + b
end

game.transition.easing.easeOut = function( t, b, c, d)
    local tt = t / d
    return -c * (tt) * (tt-2) + b
end

game.transition.easing.easeBounce = function( t, b, c, d)
    local result
    if t < .5 then result = game.transition.easing.easeOut( t * 2, b, c, d)
    else result = game.transition.easing.easeOut( 1 - ((t - .5) * 2), b, c, d)
    end
    return result
end

game.transition.easing.bounceOut = function( t, b, c, d)
    local result
    t = t / d
    if ((t) < (1/2.75)) then
        result =  c*(7.5625*t*t) + b;
    elseif (t < (2/2.75)) then
        t = t - (1.5/2.75)
        result = c*(7.5625*(t)*t + .75) + b;
    elseif (t < (2.5/2.75)) then
        t = t - (2.25/2.75)
        result = c*(7.5625*(t)*t + .9375) + b;
    else
        t = t -(2.625/2.75)
        result = c*(7.5625*(t)*t + .984375) + b;
    end
    return result
end

game.transition.easing.bounceIn = function( t, b, c, d)
    return c - game.transition.easing.bounceOut(d-t, 0, c, d) + b;
end

-- Backtracks slightly, then reverses direction and moves to end
game.transition.easing.backIn = function (t, b, c, d, s) -- s is optional; how far to overshoot
    if (s == nil) then
        s = 1.70158;
    end
    t = t / d
    return c*(t)*t*((s+1)*t - s) + b;
end

game.transition.easing.backOut = function(t, b, c, d, s) -- s is optional; how far to overshoot
    if (s == nil) then
        s = 1.70158;
    end
    t = t / d - 1
    return c*((t)*t*((s+1)*t + s) + 1) + b;
end