Skip to content

Instantly share code, notes, and snippets.

@zbinlin
Created December 2, 2014 14:59
Show Gist options
  • Save zbinlin/91a2b8d950e4d7fd6067 to your computer and use it in GitHub Desktop.
Save zbinlin/91a2b8d950e4d7fd6067 to your computer and use it in GitHub Desktop.
/*
* 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