Created
December 2, 2014 14:59
-
-
Save zbinlin/91a2b8d950e4d7fd6067 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* UnitBezier | |
* from: https://chromium.googlesource.com/chromium/blink/+/master/Source/platform/animation/UnitBezier.h | |
* version: 0.0.1 | |
*/ | |
"use strict"; | |
;(function (global, factory) { | |
if ("function" === typeof define && define.amd) { | |
define(function () { | |
return factory(); | |
}); | |
} else if ("object" === typeof module && "object" === typeof module.exports) { | |
module.exports = factory(); | |
} else { | |
global.UnitBezier = factory(); | |
} | |
})( | |
this, | |
function factory() { | |
function UnitBezier(p1x, p1y, p2x, p2y) { | |
var ax, bx, cx, | |
ay, by, cy, | |
m_startGradient, | |
m_endGradient; | |
// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1). | |
cx = 3.0 * p1x; | |
bx = 3.0 * (p2x - p1x) - cx; | |
ax = 1.0 - cx -bx; | |
cy = 3.0 * p1y; | |
by = 3.0 * (p2y - p1y) - cy; | |
ay = 1.0 - cy - by; | |
// End-point gradients are used to calculate timing function results | |
// outside the range [0, 1]. | |
// | |
// There are three possibilities for the gradient at each end: | |
// (1) the closest control point is not horizontally coincident with regard to | |
// (0, 0) or (1, 1). In this case the line between the end point and | |
// the control point is tangent to the bezier at the end point. | |
// (2) the closest control point is coincident with the end point. In | |
// this case the line between the end point and the far control | |
// point is tangent to the bezier at the end point. | |
// (3) the closest control point is horizontally coincident with the end | |
// point, but vertically distinct. In this case the gradient at the | |
// end point is Infinite. However, this causes issues when | |
// interpolating. As a result, we break down to a simple case of | |
// 0 gradient under these conditions. | |
if (p1x > 0) | |
m_startGradient = p1y / p1x; | |
else if (!p1y && p2x > 0) | |
m_startGradient = p2y / p2x; | |
else | |
m_startGradient = 0; | |
if (p2x < 1) | |
m_endGradient = (p2y - 1) / (p2x - 1); | |
else if (p2x == 1 && p1x < 1) | |
m_endGradient = (p1y - 1) / (p1x - 1); | |
else | |
m_endGradient = 0; | |
this._ax = ax; | |
this._bx = bx; | |
this._cx = cx; | |
this._ay = ay; | |
this._by = by; | |
this._cy = cy; | |
this._m_startGradient = m_startGradient; | |
this._m_endGradient = m_endGradient; | |
} | |
UnitBezier.prototype.sampleCurveX = function (t) { | |
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule. | |
return ((this._ax * t + this._bx) * t + this._cx) * t; | |
}; | |
UnitBezier.prototype.sampleCurveY = function (t) { | |
return ((this._ay * t + this._by) * t + this._cy) * t; | |
}; | |
UnitBezier.prototype.sampleCurveDerivativeX = function (t) { | |
return (3.0 * this._ax * t + 2.0 * this._bx) * t + this._cx; | |
}; | |
// Given an x value, find a parametric value it came from. | |
UnitBezier.prototype.solveCurveX = function (x, epsilon) { | |
if (x < 0.0) | |
throw new Error("x could not be less than 0.0!"); | |
if (x > 1.0) | |
throw new Error("x could not be greater than 1.0!"); | |
var t0, | |
t1, | |
t2, | |
x2, | |
d2; | |
var i; | |
// First try a few iterations of Newton's method -- normally very fast. | |
for (t2 = x, i = 0; i < 8; i++) { | |
x2 = this.sampleCurveX(t2) - x; | |
if (Math.abs(x2) < epsilon) | |
return t2; | |
d2 = this.sampleCurveDerivativeX(t2); | |
if (Math.abs(d2) < 1e-6) | |
break; | |
t2 = t2 - x2 / d2; | |
} | |
// Fall back to the bisection method for reliability. | |
t0 = 0.0; | |
t1 = 1.0; | |
t2 = x; | |
while (t0 < t1) { | |
x2 = this.sampleCurveX(t2); | |
if (Math.abs(x2 - x) < epsilon) | |
return t2; | |
if (x > x2) | |
t0 = t2; | |
else | |
t1 = t2; | |
t2 = (t1 - t0) * .5 + t0; | |
} | |
// Failure. | |
return t2; | |
}; | |
// Evaluates y at the given x. The epsilon parameter provides a hint as to the required | |
// accuracy and is not guaranteed. | |
UnitBezier.prototype.solve = function (x, epsilon) { | |
if (x < 0.0) | |
return 0.0 + this.m_startGradient * x; | |
if (x > 1.0) | |
return 1.0 + this.m_endGradient * (x - 1.0); | |
return this.sampleCurveY(this.solveCurveX(x, epsilon)); | |
}; | |
return UnitBezier; | |
} | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment