|
| 1 | +-- ease - a modified remix of flux and bezier-easing. |
| 2 | +-- https://github.com/poke1024/ease.git |
| 3 | + |
| 4 | +-- the following code is adapted from flux, Copyright (c) 2016 rxi, |
| 5 | +-- https://github.com/rxi/flux/. |
| 6 | + |
| 7 | +local ease = { linear = function(p) return p end } |
| 8 | + |
| 9 | +local penner = { |
| 10 | + quad = "p * p", |
| 11 | + cubic = "p ^ 3", |
| 12 | + quart = "p ^ 4", |
| 13 | + quint = "p ^ 5", |
| 14 | + expo = "2 ^ (10 * (p - 1))", |
| 15 | + sine = "-cos(p * (pi * .5)) + 1", |
| 16 | + circ = "-(sqrt(1 - (p * p)) - 1)", |
| 17 | + back = "p * p * (2.7 * p - 1.7)", |
| 18 | + elastic = "-(2^(10 * (p - 1)) * sin((p - 1.075) * (pi * 2) / .3))", |
| 19 | + bounce = "bounce(p)" |
| 20 | +} |
| 21 | + |
| 22 | +local bounce = function(t) |
| 23 | + t = 1 - t |
| 24 | + if t < (1 / 2.75) then |
| 25 | + return 1 - 7.5625 * t * t |
| 26 | + elseif t < (2 / 2.75) then |
| 27 | + t = t - 1.5 / 2.75 |
| 28 | + return 1 - (7.5625 * t * t + .75) |
| 29 | + elseif t < (2.5 / 2.75) then |
| 30 | + t = t - 2.25 / 2.75 |
| 31 | + return 1 - (7.5625 * t * t + .9375) |
| 32 | + else |
| 33 | + t = t - 2.625 / 2.75 |
| 34 | + return 1 - (7.5625 * t * t + .984375) |
| 35 | + end |
| 36 | +end |
| 37 | + |
| 38 | +local load = loadstring or load |
| 39 | + |
| 40 | +local symbols = { |
| 41 | + math = math, |
| 42 | + bounce = bounce |
| 43 | +} |
| 44 | + |
| 45 | +local compile = function(name, str, expr) |
| 46 | + ease[name] = load([[ |
| 47 | + local sin = math.sin; local cos = math.cos; local pi = math.pi; local sqrt = math.sqrt; |
| 48 | + return function(p) ]] .. str:gsub("%$e", expr) .. " end", name, "t", symbols)() |
| 49 | +end |
| 50 | + |
| 51 | +for k, v in pairs(penner) do |
| 52 | + compile("in" .. k, "return $e", v) |
| 53 | + compile("out" .. k, [[ |
| 54 | + p = 1 - p |
| 55 | + return 1 - ($e) |
| 56 | + ]], v) |
| 57 | + compile("inout" .. k, [[ |
| 58 | + p = p * 2 |
| 59 | + if p < 1 then |
| 60 | + return .5 * ($e) |
| 61 | + else |
| 62 | + p = 2 - p |
| 63 | + return .5 * (1 - ($e)) + .5 |
| 64 | + end |
| 65 | + ]], v) |
| 66 | +end |
| 67 | + |
| 68 | +-- the following code is a lua port of Gaëtan Renaudeau's JavaScript library bezier-easing, |
| 69 | +-- https://github.com/gre/bezier-easing (ported from v2.0.3, e0036aa16e36d3647413013fa774d3df37f348f1). |
| 70 | + |
| 71 | +-- These values are established by empiricism with tests (tradeoff: performance VS precision) |
| 72 | +local NEWTON_ITERATIONS = 4 |
| 73 | +local NEWTON_MIN_SLOPE = 0.001 |
| 74 | +local SUBDIVISION_PRECISION = 0.0000001 |
| 75 | +local SUBDIVISION_MAX_ITERATIONS = 10 |
| 76 | + |
| 77 | +local kSplineTableSize = 11 |
| 78 | +local kSampleStepSize = 1.0 / (kSplineTableSize - 1.0) |
| 79 | + |
| 80 | +local function A (aA1, aA2) return 1.0 - 3.0 * aA2 + 3.0 * aA1 end |
| 81 | +local function B (aA1, aA2) return 3.0 * aA2 - 6.0 * aA1 end |
| 82 | +local function C (aA1) return 3.0 * aA1 end |
| 83 | + |
| 84 | +-- Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. |
| 85 | +local function calcBezier (aT, aA1, aA2) return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT end |
| 86 | + |
| 87 | +-- Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. |
| 88 | +local function getSlope (aT, aA1, aA2) return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1) end |
| 89 | + |
| 90 | +local abs = math.abs |
| 91 | + |
| 92 | +local function binarySubdivide (aX, aA, aB, mX1, mX2) |
| 93 | + local currentX, currentT |
| 94 | + for i = 1, SUBDIVISION_MAX_ITERATIONS do |
| 95 | + currentT = aA + (aB - aA) / 2.0 |
| 96 | + currentX = calcBezier(currentT, mX1, mX2) - aX |
| 97 | + if currentX > 0.0 then |
| 98 | + aB = currentT |
| 99 | + else |
| 100 | + aA = currentT |
| 101 | + end |
| 102 | + if abs(currentX) <= SUBDIVISION_PRECISION then |
| 103 | + break |
| 104 | + end |
| 105 | + end |
| 106 | + return currentT |
| 107 | +end |
| 108 | + |
| 109 | +local function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) |
| 110 | + for i = 1, NEWTON_ITERATIONS do |
| 111 | + local currentSlope = getSlope(aGuessT, mX1, mX2) |
| 112 | + if currentSlope == 0.0 then |
| 113 | + return aGuessT |
| 114 | + end |
| 115 | + local currentX = calcBezier(aGuessT, mX1, mX2) - aX |
| 116 | + aGuessT = aGuessT - currentX / currentSlope |
| 117 | + end |
| 118 | + return aGuessT |
| 119 | +end |
| 120 | + |
| 121 | +local newSampleValues |
| 122 | +if type(jit) == "table" then -- running under LuaJIT? |
| 123 | + local ffi = require("ffi") |
| 124 | + local spec = "float[" .. tostring(kSplineTableSize + 1) .. "]" |
| 125 | + newSampleValues = function() return ffi.new(spec) end |
| 126 | +else |
| 127 | + newSampleValues = function() return {} end |
| 128 | +end |
| 129 | + |
| 130 | +ease.cubicbezier = function (mX1, mY1, mX2, mY2) |
| 131 | + if not (0 <= mX1 and mX1 <= 1 and 0 <= mX2 and mX2 <= 1) then |
| 132 | + error('bezier x values must be in [0, 1] range') |
| 133 | + end |
| 134 | + |
| 135 | + if mX1 == mY1 and mX2 == mY2 then |
| 136 | + return ease.linear |
| 137 | + end |
| 138 | + |
| 139 | + local sampleValues = newSampleValues() |
| 140 | + for i = 0, kSplineTableSize - 1 do |
| 141 | + sampleValues[i + 1] = calcBezier(i * kSampleStepSize, mX1, mX2) |
| 142 | + end |
| 143 | + local lastSample = kSplineTableSize - 1 |
| 144 | + |
| 145 | + local function getTForX (aX) |
| 146 | + local intervalStart = 0.0 |
| 147 | + local currentSample = 1 |
| 148 | + |
| 149 | + while currentSample ~= lastSample and sampleValues[currentSample + 1] <= aX do |
| 150 | + currentSample = currentSample + 1 |
| 151 | + intervalStart = intervalStart + kSampleStepSize |
| 152 | + end |
| 153 | + currentSample = currentSample - 1 |
| 154 | + |
| 155 | + -- Interpolate to provide an initial guess for t |
| 156 | + local dist = (aX - sampleValues[currentSample + 1]) / (sampleValues[currentSample + 2] - sampleValues[currentSample + 1]) |
| 157 | + local guessForT = intervalStart + dist * kSampleStepSize |
| 158 | + |
| 159 | + local initialSlope = getSlope(guessForT, mX1, mX2) |
| 160 | + if initialSlope >= NEWTON_MIN_SLOPE then |
| 161 | + return newtonRaphsonIterate(aX, guessForT, mX1, mX2) |
| 162 | + elseif initialSlope == 0.0 then |
| 163 | + return guessForT |
| 164 | + else |
| 165 | + return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2) |
| 166 | + end |
| 167 | + end |
| 168 | + |
| 169 | + return function (x) |
| 170 | + -- Because Lua numbers are imprecise, we should guarantee the extremes are right. |
| 171 | + if x == 0 then |
| 172 | + return 0 |
| 173 | + elseif x == 1 then |
| 174 | + return 1 |
| 175 | + else |
| 176 | + return calcBezier(getTForX(x), mY1, mY2) |
| 177 | + end |
| 178 | + end |
| 179 | +end |
| 180 | + |
| 181 | +return ease |
0 commit comments