2

My goal is to write an animation in JavaScript that performs an ease-in-out style bezier curve animation (such as in http://cubic-bezier.com/#.42,0,.58,1)

I came up with the following script to calculate the y value given "x" value (time):

function CalculateBezierPoint(t, p0, p1, p2, p3) {
  var y = ((1-t)*(1-t)*(1-t)*p0) + (3*(1-t)*(1-t)*t*p1) + (3*(1-t)*t*t*p2) + (t*t*t*p3);
  return y;
}

Using the explicit formula from Wikipedia : Cubic Bezier Wiki Formula

Demo - https://codepen.io/anon/pen/QpRzBg

However, the print statements show the Y value going down before going up, when it should ONLY be going up:

0.42
0.3228427793606603
0.3119941308275725
0.3025864871426283
0.29458762995005683
0.2879653408940873
0.28268740161894895
0.27872159376887096
0.27603569898808256
0.27459749892081287
0.2743747752112911
0.2753353095037466
0.27744688344240837
0.28067727867150566
0.2849942768352678
0.29675920854370297
0.3041427053768346
0.3124839317215477
0.3217506690862967
0.33191069937460593
0.34293180410763435
0.35478176492961133

I did manage to find someone else's code that seems to work, here is the output:

0
0.009480343767040133
0.0246451904411195
0.03199616010201068
0.040680303103589804
0.05080871722437687
0.062492500242891866
0.07584274993765482
0.0909705640871857
0.10798704047000454
0.12700327686463134
0.14813037104958607
0.17147942080338874
0.19716152390455938
0.225287778131618
0.25596928126308455
0.28931713107747903
0.3254424253533215
0.36445626186913194
0.4064697384034303
0.4515939527347367
0.499940002641571

Demo - https://codepen.io/anon/pen/evabrr

Both demos use the same input: p0 = .42, p1 = 0, p2 = .58, p3 = 1

I don't know why my attempt fails, and the code I found works. Did I implement the formula wrong? Did I choose the wrong formula? Something else?

HC_
  • 1,040
  • 10
  • 23
  • I think I'm actually making a big error -- the P are supposed to be point values.. which should have (x,y) and I'm only providing one digit? – HC_ Apr 11 '17 at 17:33

3 Answers3

3

So your mistake is neither passing a single value nor the power function. The mistake is that you are assuming that x = t.

I came up with the following script to calculate the y value given "x" value (time):

If x=t then what you have is an explicit bezier curve, not a parametric bezier curve. Explicit meaning that y is a function of x (i.e. y=f(x)). As opposed to a parametric equation where both x and y are functions of t (i.e. x=f(t) and y=f(t)).

One way to test that this is truly the case, set your x values to [0, 1/3, 2/3, 1]. Evenly spaced x-values ensures that x=t and will give you an explicit bezier curve. You can achieve this on http://cubic-bezier.com by setting the x values in the address bar to 0.333 and 0.666. But as soon as you move the control points to either the left or the right your results will again differ.

To get the same effect it is a bit more involved. You must solve for t at a given x and then calculate y from t. Solving for t is a little complex but can be approximated with the newton-raphson method. This link does a much better job explaining how to implement it: http://greweb.me/2012/02/bezier-curve-based-easing-functions-from-concept-to-implementation/

GCB613
  • 174
  • 13
1

Late to the party, but: your comment "I think I'm actually making a big error -- the P are supposed to be point values.. which should have (x,y) and I'm only providing one digit" is correct.

A Bezier curve is a parametric function where both x and y (or x, y, and z in 3D) are functions of t. You're only calculating half of the curve, so you need to modify your CalculateBezierPoint function to return an x/y coordinate, not just a y coordinate:

calculateBezierPoint(t, xvalues, yvalues) {
  return new Point(
    x = calculateBezierDim(t, x1, x2, x3, x4),
    y = calculateBezierDim(t, y1, y2, y3,y4)
  );
}

calculateBezierDim(t, vals) {
  a=vals[0], b=vals[1], c=vals[2], d=vals[3];
  mt = 1-t;
  t2 = t*t;
  mt2 = mt*mt;
  return a * mt*mt2 + 3 * b * mt2 * t + 3 * c * mt * t2 + d * t2 * t;
}

(Adapted to your programming language and data types of course).

Then you can draw that x/y coordinate instead.

step = some small value
S = calculateBezierPoint(0, xvals, yvals)
for(t=step; t<1+step; t+=step) {
  E = calculateBezierPoint(t, xvals, yvals)
  drawLine(S.x, S.y, E.x, E.y)
  S = E
}
Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
0

I think there could be an error in your formula, with operator precedence. I would try using the exponentiation function where appropriate, would make things easier to debug.

Math.pow(base, exponent)
Daniel AG
  • 320
  • 1
  • 9
  • Yeah it's very possible.. just tried to implement it using `Math.pow` over here: https://codepen.io/anon/pen/xqNMpG and got very similar (but not exactly same) output as in my first buggy attempt. – HC_ Apr 07 '17 at 00:55