You don't want that! You want this!
'Eased' motion and 1D Beziers
You can solve a cubic, but it's tricky and probably not what you want
The fact that a cubic generally has three solutions is a big clue that 2D Beziers are powerful enough to draw totally inappropriate graphs for a single-valued function like yours.
(By a single-valued function I mean one that won't ever wrap back on itself in the x-direction.)
If you're looking for an attractive interpolation of discrete values, however, 2D Beziers are very convenient, especially as they're implemented in almost all drawing packages; so indeed 2D Beziers often are used to draw graphs of single-valued functions.
In such a case, it is best to make sure that each Bezier's two control handles share their horizontal coordinate, which would always lie halfway between the horizontal coordinates of the Bezier's end points. And that will cause the horizontal coordinate to vary linearly with t, and NOT as a cubic.
That effectively limits the possibilities of a 2D Bezier to those of a 1D Bezier. It's much more appropriate for a single-valued function, and of course makes it much easier to find y in terms of x.
Consider a 1D equivalent
Not all Beziers are as much as 2D. In fact, easing (the technical name for the motion you are trying to achieve) is usually achieved with a 1D Bezier. There is even a specification in SVG for defining a custom easing on an animation, by specifying the end values and control values for a 1D Bezier.
Control values, in 1D as in 2D, are end values plus some difference value, which is related to the desired gradient of the dependent variable, with respect to the independent variable. In particular, it happens to be exactly half the desired gradient. In your case, you want a gradient of zero at both ends of the eased motion, so your control values should be the same as your end values. (This corresponds to the fact that your control handles are horizontal, giving a zero gradient of y with respect to the independent variable.)
Option 1: Construct a polynomial and step through it with constant time increments
If you have a pair of values and their gradients with respect to the independent variable, you can interpolate them with a Cartesian cubic, in the form
y = a.t^3 + b.t^2 + c.t + d
If you decide on values for more derivatives, the same method can lead you to higher orders of polynomial. So to specify end accelerations, you need the versatility of a 5th order polynomial, and to specify jerk and jounce, you need the 7th and 9th orders respectively. This adds expressiveness to the curve without requiring you to find the roots of anything!
I'll explore the method here for just the cubic version.
This is effectively a one dimensional case of what you would do to construct a 2D Bezier. Again, you are fixing a position and a gradient at each end of the curve.
For some extra background: The control handles you're used to, on a 2D Bezier, automatically determine the gradients of x and y with respect to the independent variable t. The gradients are twice the size of the horizontal and vertical components, respectively. (Scaling them together, away from this value, gives other valid curves of the same order, maintaining the tangent direction, but they would compromise the recursive interpolation properties of a Bezier and its hull.)
In a 1D case, you need to decide the gradient of y with respect to x, by whatever means is appropriate for your application. There are many algorithms you could choose for that. It is just like choosing the positions of control handles. In your case, you want a gradient of zero at both ends of the easing.
Once you've chosen your end values, you have some (fairly simple) simultaneous equations to determine the values of a, b and c.
To make things easier, let's suppose that t runs from 0 to 1. I would recommend implementing it this way; actual time values for a particular animation can be mapped to and from this time-scale as needed. So if the actual animation starts at start and lasts for duration, just use
T = start + duration.t and t = (T - start)/duration
That way, at one end of the line, the equations for y and its gradient,
y = a.t^3 + b.t^2 + c.t + d
dy/dt = 3a.t^2 + b.t + c
simply collapse all the way down to
y = d
dy/dt = c
So from the value of the function and the decided-on gradient at t = 0, you already have two terms of the polynomial. At the t = 1 end of the motion, we find
1. y = a + b + c + d
2. dy/dt = 3a + 2b + c
and finding a and b by elimination is now easy.
Equation 2. - 2 x Equation 1. to eliminate b and calculate a
3 x Equation 1. - Equation 2. to eliminate a and calculate b
Option 2: Use in-built Bezier functionality, and just specify end/control values
This would probably be done most easily by more thoroughly investigating the easing functions available to you in the language you're using.
My personal fallback, if there is no such functionality, would be the above method of finding a suitable polynomial to step through.
But you already appear to be almost there, with your solution, so I can't resist adding what is needed, to complete it.
So you can clearly can draw a Bezier somehow.
And you appear to have decided to get one coordinate of that Bezier --
again, somehow -- so then you can use it as your easing value. Okay.
Modify your particular Bezier to be linear in the horizontal axis
Certain special cases of Bezier are worth noting here.
- When the control points have identical x values, x will only be quadratic in t. Likewise, if they have identical y values, y will only be quadratic in t. When both components are identical (so there is only one, shared control point), the Bezier is entirely quadratic.
- When the shared x value is equidistant between the end points, x will only be linear in t. Likewise, if a shared y value lies halfway, y will only be linear in t. For a shared control point halfway between the end points, the Bezier is entirely linear. (The curve is straight and is traced at constant speed.)
So by setting the x coordinates of the control handles to be halfway between the x coordinates of the end points, you will construct the special case that has the parametric form
x = a.t^3 + b.t^2 + c.t + d, with a = 0, b = 0 making it LINEAR
y = e.t^3 + f.t^2 + g.t + e
Once again, you now have a linear relationship between x and t, so you can easily express y in terms of x by making a substitution in the explicit expression for y.
Because I Don't Like To Leave Anything Unanswered...
Do you still have need of a cubic solver? And can you say more about your difficulties?
A word of warning about naive equations
You definitely do want to solve the linear equation AS a linear equation. Don't be thinking that something more general must also work on lower orders of equation. Even if you write a cubic solver, it will actually break in this case.
Trying to solve a lower order equation with techniques for higher orders will tend to lead you into a division by zero. In a simpler case, consider solving the linear equation,
B.t + C = 0
with the general formula for quadratics,
t = (-B +- sqrt(B^2 - 4A.C)) / 2A, with A = 0
This gives the answers finite/zero and zero/zero, which aren't particularly useful even if you can interpret them as correct. Each is easy to understand if you watch what happens to the solutions as the value of A vanishes.
Another word of warning about cubic and quartic solvers
A lot is already available on how to do this, either symbolically or numerically. I can't add much here, to what's already available, especially with the scant clues I have about what level you're at. (You haven't said much about your progress so far.)
But the standard warnings bear repeating:
- Symbolic solvers are often slower at achieving the same accuracy.
- When you vary the terms of the polynomial smoothly, the true roots should vary smoothly, but the calculated roots often won't, being sensitive to rounding errors in your calculation. (That's mainly true of the symbolic solvers.) This is what is meant by the 'numeric stability' of your roots. You want smooth motion so this is an issue.
- Are you sure you want to spend performance on root finding, in the inside loop of an animation, given the huge number of explicit functions you could use for easing?