1dayitwillmake
1dayitwillmake

Reputation: 2339

Recreating CSS3 transitions Cubic-Bezier curve

In CSS3 transitions, you can specify a timing function as 'cubic-bezier:(0.25, 0.3, 0.8, 1.0)' In that string, you are only specifying the XY for points P1 and P2 along the curve, as P0 and P3 are always (0.0, 0.0), and (1.0, 1.0) respectively.

According to Apple's site: x [is] expressed as a fraction of the overall duration and y expressed as a fraction of the overall change

My question is how can this be mapped back to a traditional 1 dimensional T value in javascript?

--

From Apple docs on animating with transitions
enter image description here

Upvotes: 8

Views: 6973

Answers (3)

technobroken
technobroken

Reputation: 21

I have try and search a lot of time and forms and definetly i have reached one simple and fast. The trick is get the cubic bezier function in this form: P(u) = u^3(c0 + 3c1 -3c2 +c3) + u^2(3c0 -6c1 +3c2) + u(-3c0 +3c1) + c0 where ci are the control points. The other part is search y from x with a binary search.

static public class CubicBezier {
    private BezierCubic bezier = new BezierCubic();
    public CubicBezier(float x1, float y1, float x2, float y2) {
        bezier.set(new Vector3(0,0,0), new Vector3(x1,y1,0), new Vector3(x2,y2,0), new Vector3(1,1,1));
    }
    public float get(float t) {
        float l=0, u=1, s=(u+l)*0.5f;
        float x = bezier.getValueX(s);
        while (Math.abs(t-x) > 0.0001f) {
            if (t > x)  { l = s; }
            else        { u = s; }
            s = (u+l)*0.5f;
            x = bezier.getValueX(s);
        }
        return bezier.getValueY(s);
    }
};

public class BezierCubic {
private float[][] cpoints = new float[4][3];
private float[][] polinom = new float[4][3];

public BezierCubic() {}

public void set(Vector3 c0, Vector3 c1, Vector3 c2, Vector3 c3) {
    setPoint(0, c0);
    setPoint(1, c1);
    setPoint(2, c2);
    setPoint(3, c3);
    generate();
}

public float getValueX(float u) {
    return getValue(0, u);
}

public float getValueY(float u) {
    return getValue(1, u);
}

public float getValueZ(float u) {
    return getValue(2, u);
}

private float getValue(int i, float u) {
    return ((polinom[0][i]*u + polinom[1][i])*u + polinom[2][i])*u + polinom[3][i];
}

private void generate() {
    for (int i=0; i<3; i++) {
        float c0 = cpoints[0][i], c1 = cpoints[1][i], c2 = cpoints[2][i], c3 = cpoints[3][i];
        polinom[0][i] = c0 + 3*(c1 - c2) + c3;
        polinom[1][i] = 3*(c0 - 2*c1 + c2);
        polinom[2][i] = 3*(-c0 + c1);
        polinom[3][i] = c0;
    }
}

private void setPoint(int i, Vector3 v) {
    cpoints[i][0] = v.x;
    cpoints[i][1] = v.y;
    cpoints[i][2] = v.z;
}

}

Upvotes: 0

1dayitwillmake
1dayitwillmake

Reputation: 2339

Browsing through webkit-source a bit, the following code will give the correct T value for the implicit curve used in CSS3 transitions:

Visual demo (codepen.io)

Hope this helps someone!

function loop(){
    var t = (now - animationStartTime) / ( animationDuration*1000 );

    var curve = new UnitBezier(Bx, By, Cx, Cy);
    var t1 = curve.solve(t, UnitBezier.prototype.epsilon);
    var s1 = 1.0-t1;

    // Lerp using solved T
    var finalPosition.x = (startPosition.x * s1) + (endPosition.x * t1);
    var finalPosition.y = (startPosition.y * s1) + (endPosition.y * t1);
}


/**
* Solver for cubic bezier curve with implicit control points at (0,0) and (1.0, 1.0)
*/
function UnitBezier(p1x, p1y, p2x, p2y) {
    // pre-calculate the polynomial coefficients
    // First and last control points are implied to be (0,0) and (1.0, 1.0)
    this.cx = 3.0 * p1x;
    this.bx = 3.0 * (p2x - p1x) - this.cx;
    this.ax = 1.0 - this.cx -this.bx;

    this.cy = 3.0 * p1y;
    this.by = 3.0 * (p2y - p1y) - this.cy;
    this.ay = 1.0 - this.cy - this.by;
}

UnitBezier.prototype.epsilon = 1e-6; // Precision  
UnitBezier.prototype.sampleCurveX = function(t) {
    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;
}


UnitBezier.prototype.solveCurveX = function (x, epsilon) {
    var t0; 
    var t1;
    var t2;
    var x2;
    var 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) < epsilon)
            break;
        t2 = t2 - x2 / d2;
    }

    // No solution found - use bi-section
    t0 = 0.0;
    t1 = 1.0;
    t2 = x;

    if (t2 < t0) return t0;
    if (t2 > t1) return t1;

    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;
    }

    // Give up
    return t2;
}

// Find new T as a function of Y along curve X
UnitBezier.prototype.solve = function (x, epsilon) {
    return this.sampleCurveY( this.solveCurveX(x, epsilon) );
}

Upvotes: 22

Zach Shipley
Zach Shipley

Reputation: 1092

You want to find the [0,1] value for any time value t [0,1]? There's a well-defined equation for a cubic bezier curve. Wikipedia page: http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves

So I don't have to type out their (probably LaTeX-formatted) formula, I copy-pasted the same formula from http://local.wasp.uwa.edu.au/~pbourke/geometry/bezier/index2.html . This also has a C implementation which, on a quick read-through, should be easy to port to javascript:

B(u) = P0 * ( 1 - u )3 + P1 * 3 * u * ( 1 - u )2 + P2 * 3 * u2 * ( 1 - u ) + P3 * u3

What he's calling mu on that page is your time variable t.

Edit: If you don't want to do the math it looks like someone already wrote a small utility library in javascript to do basic bezier curve math: https://github.com/sporritt/jsBezier . pointOnCurve(curve, location) looks like just what you're asking for.

Upvotes: 1

Related Questions