UNPKG

redshift

Version:

A JavaScript UX framework. Handles animation, UI physics and user input tracking.

166 lines (128 loc) 5.36 kB
/* Bezier function generator Gaëtan Renaudeau's BezierEasing https://github.com/gre/bezier-easing/blob/master/index.js https://github.com/gre/bezier-easing/blob/master/LICENSE You're a hero Use var easeOut = new Bezier(.17,.67,.83,.67), x = easeOut(0.5); // returns 0.627... */ "use strict"; var NEWTON_ITERATIONS = 8, NEWTON_MIN_SLOPE = 0.001, SUBDIVISION_PRECISION = 0.0000001, SUBDIVISION_MAX_ITERATIONS = 10, K_SPLINE_TABLE_SIZE = 11, K_SAMPLE_STEP_SIZE = 1.0 / (K_SPLINE_TABLE_SIZE - 1.0), FLOAT_32_SUPPORTED = 'Float32Array' in global, A = function (a1, a2) { return 1.0 - 3.0 * a2 + 3.0 * a1; }, B = function (a1, a2) { return 3.0 * a2 - 6.0 * a1; }, C = function (a1) { return 3.0 * a1; }, getSlope = function (t, a1, a2) { return 3.0 * A(a1, a2) * t * t + 2.0 * B(a1, a2) * t + C(a1); }, calcBezier = function (t, a1, a2) { return ((A(a1, a2) * t + B(a1, a2)) * t + C(a1)) * t; }, /* Bezier constructor */ Bezier = function (mX1, mY1, mX2, mY2) { var sampleValues = FLOAT_32_SUPPORTED ? new Float32Array(K_SPLINE_TABLE_SIZE) : new Array(K_SPLINE_TABLE_SIZE), _precomputed = false, binarySubdivide = function (aX, aA, aB) { var currentX, currentT, i = 0; do { currentT = aA + (aB - aA) / 2.0; currentX = calcBezier(currentT, mX1, mX2) - aX; if (currentX > 0.0) { aB = currentT; } else { aA = currentT; } } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); return currentT; }, newtonRaphsonIterate = function (aX, aGuessT) { var i = 0, currentSlope = 0.0, currentX; for (; i < NEWTON_ITERATIONS; ++i) { currentSlope = getSlope(aGuessT, mX1, mX2); if (currentSlope === 0.0) { return aGuessT; } currentX = calcBezier(aGuessT, mX1, mX2) - aX; aGuessT -= currentX / currentSlope; } return aGuessT; }, calcSampleValues = function () { for (var i = 0; i < K_SPLINE_TABLE_SIZE; ++i) { sampleValues[i] = calcBezier(i * K_SAMPLE_STEP_SIZE, mX1, mX2); } }, getTForX = function (aX) { var intervalStart = 0.0, currentSample = 1, lastSample = K_SPLINE_TABLE_SIZE - 1, dist = 0.0, guessForT = 0.0, initialSlope = 0.0; for (; currentSample != lastSample && sampleValues[currentSample] <= aX; ++currentSample) { intervalStart += K_SAMPLE_STEP_SIZE; } --currentSample; dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample+1] - sampleValues[currentSample]); guessForT = intervalStart + dist * K_SAMPLE_STEP_SIZE; initialSlope = getSlope(guessForT, mX1, mX2); // If slope is greater than min if (initialSlope >= NEWTON_MIN_SLOPE) { return newtonRaphsonIterate(aX, guessForT); // Slope is equal to min } else if (initialSlope === 0.0) { return guessForT; // Slope is less than min } else { return binarySubdivide(aX, intervalStart, intervalStart + K_SAMPLE_STEP_SIZE); } }, precompute = function () { _precomputed = true; if (mX1 != mY1 || mX2 != mY2) { calcSampleValues(); } }, /* Generated function Returns value 0-1 based on X */ f = function (aX) { var returnValue; if (!_precomputed) { precompute(); } // If linear gradient, return X as T if (mX1 === mY1 && mX2 === mY2) { returnValue = aX; // If at start, return 0 } else if (aX === 0) { returnValue = 0; // If at end, return 1 } else if (aX === 1) { returnValue = 1; } else { returnValue = calcBezier(getTForX(aX), mY1, mY2); } return returnValue; } return f; }; module.exports = Bezier;