I've read a few discussions regarding finding Y at X for a cubic Bezier curve, and have also read this article regarding the matter.
My case is more constrained than the general one, and I wonder if there's a better solution than the general ones mentioned in the above discussions.
My case:
- The
X
value of the different control points is increasing. Ie:X3 > X2 > X1 > X0
. - Also, as a result of above,
X(t)
is strictly monotonically increasing as well.
Is there any efficient algorithm that takes such constraints into account?
If a binary search is too complex, there is still an
O(1)
approach but its fairly limited. I assume you are using a 4 control point (p0(x0,y0),p1(x1,y1),p2(x2,y2),p3(x3,y3)
) cubic Bezier parametrized by somet
in the interval[0.0 , 1.0]
so:First lets forget about Beziers for a while and use a catmull-rom curve instead, which is just an alternative way to represent the same curve. To convert between the 2 cubics use these:
where
(xi,yi)
are Bezier control points and(Xi,Yi)
are Catmull-Rom points. Now if theX
distance between all control points have the same distance:then the
X
coordinate is linear witht
. That means we can computet
directly fromX
:Now we can compute the
Y
for anyX
directly. So if you can chose the control points then chose them so they meet the X distance condition.If the condition is not met you can try to change control points so it is satisfied (by binary search, by subdividing the cubics into more patches etc...) but beware changing control points can change the shape of the resulting curve if not careful.
First off: this answer only works because your control point constraint means we're always dealing with a parametric equivalent of a normal function. Which is obviously what you want in this case, but anyone who finds this answer in the future should be aware that this answer is based on the assumption that there is only one y value for any given x value. Which is, of course, absolutely not true for Bezier curves in general.
With that said, we know that—even though we've expressed this curve as a parametric curve in two dimensions—we're dealing with a curve that for all intents and purposes must have some unknown function of the form
y = f(x)
. We also know that as long as we know the "t" value that uniquely belongs to a specific x (which is only the case because of your strictly monotonically increasing coefficients property), we can compute y asy = By(t)
, so the question is: can we compute thet
value that we need to plug intoBy(t)
, given some knownx
value?To which the answer is: yes, we can.
First, any
x
value we start off with can be said to come fromx = Bx(t)
, so given that we knowx
, we should be able to find the corresponding value(s) oft
that leads to thatx
.let's look at the function for x(t):
We can rewrite this to a plain polynomial form as:
This is a standard cubic polynomial, with only known constants as coefficients, and we can trivially rewrite this to:
You might be wondering "where did all the -x go for the other values a, b, c, and d?" and the answer there is that they all cancel out, so the only one we actually end up needing to subtract is the one all the way at the end. Handy!
So now we just... solve this equation: we know everything except
t
, we just need some mathematical insight to tell us how to do this....Of course "just" is not the right qualifier here, there is nothing "just" about finding the roots of a cubic function, but thankfully, Gerolano Cardano laid the ground works to determining the roots back in the 16th century, using complex numbers. Before anyone had even invented complex numbers. Quite a feat! But this is a programming answer, not a history lesson, so let's get implementing:
Given some known value for x, and a knowledge of our coordinates a, b, c, and d, we can implement our root-finding as follows:
Okay, that's quite the slab of code, with quite a few additionals:
crt()
is the cuberoot function. We actually don't care about complex numbers in this case so the easier way to implement this is with a def, or macro, or ternary, or whatever shorthand your language of choice offers:crt(x) = x < 0 ? -pow(-x, 1f/3f) : pow(x, 1f/3f);
.tau
is just 2π. It's useful to have around when you're doing geometry programming.approximately
is a function that compares a value to a very small interval around the target because IEEE floating point numerals are jerks. Basically we're talking aboutapproximately(a,b) = return abs(a-b) < 0.000001
or something.The rest should be fairly self-explanatory, if a little java-esque (I'm using Processing for these kind of things).
With this implementation, we can write our implementation to find y, given x. Which is a little more involved than just calling the function because cubic roots are complicated things. You can get up to three roots back. But only one of them will be applicable to our "t interval" [0,1]:
And that's it, we're done: we now have the "t" value that we can use to get the associated "y" value.