Coordinates of point on cubic bezier in Javascript

59 Views Asked by At

I'm trying to create a JS function that will return a Y coordinate on a cubic bezier given the X coordinate. Here's what I have:

function pointOnBezier(t, p0, p1, p2, p3) {
   var x = Math.pow(1-t,3) * p0[0] + 3 * t * Math.pow(1 - t, 2) * p1[0] + 3 * t * t * (1 - t) * p2[0] + t * t * t * p3[0];
   var y = Math.pow(1-t,3) * p0[1] + 3 * t * Math.pow(1 - t, 2) * p1[1] + 3 * t * t * (1 - t) * p2[1] + t * t * t * p3[1];
  return [x, y];
}

console.log(0.25, pointOnBezier(0.25, [0,0], [0.5, 0.5], [0.5, 0.5], [1.0, 1.0]));
console.log(0.5, pointOnBezier(0.5, [0,0], [0.5, 0.5], [0.5, 0.5], [1.0, 1.0]));
console.log(0.75, pointOnBezier(0.75, [0,0], [0.5, 0.5], [0.5, 0.5], [1.0, 1.0]));

I included the three test values. If you run the code, the expected results of t=0.25 and t=0.75 is a value of 0.25 and 0.75 but it's returning 0.3 and 0.7.

Can anyone see the problem with the calculations?

Edit: while similar to other questions, I've looked at the answers there and so far I haven't found a working solution plus the discussion has continued about the merits of two common techniques.

1

There are 1 best solutions below

15
Guillaume Brunerie On

There are three values involved in such Bezier curves: t which is the input to the function, and x and y which are the output. Your code returns the value of x and y for a given value of t, but there is no reason t would be related to x in any obvious way.

Generally speaking it is easy to get x and y from t (your code), but it is more complicated to get y from x. The only One way to do it is by binary search: try some values of t until you get close enough to the desired x, which also gets you the y you are looking for.

Example code (untested and assuming the bezier curve is increasing):

const EPSILON = 0.00001;
const getY = (x, p0, p1, p2, p3) => {
    let minT = 0;
    let maxT = 1;
    while (maxT - minT > EPSILON) {
        const t = (minT + maxT) / 2;
        const newX = pointOnBezier(t, p0, p1, p2, p3)[0];
        if (newX > x) {
            maxT = t;
        } else {
            minT = t;
        } 
    }
    return pointOnBezier(minT, p0, p1, p2, p3)[1];
};

Edit: As mentioned in the comments, another way to do it without binary search would be to solve the third degree equation mathematically to directly get the correct value of t, and then calculate the corresponding y. However this would most likely lead to significantly more complicated code, for no clear benefit.