redshift
Version:
A JavaScript UX framework. Handles animation, UI physics and user input tracking.
166 lines (128 loc) • 5.36 kB
JavaScript
/*
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...
*/
;
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;