63

I'd like to calculate a point on a quadratic curve. To use it with the canvas element of HTML5.

When I use the quadraticCurveTo() function in JavaScript, I have a source point, a target point and a control point.

How can I calculate a point on the created quadratic curve at let's say t=0.5 with "only" knowing this three points?

Paolo Forgia
  • 6,572
  • 8
  • 46
  • 58
Christian Engel
  • 3,738
  • 5
  • 28
  • 44

5 Answers5

132

Use the quadratic Bézier formula, found, for instance, on the Wikipedia page for Bézier Curves:

quadratic Bezier formula

In pseudo-code, that's

t = 0.5; // given example value
x = (1 - t) * (1 - t) * p[0].x + 2 * (1 - t) * t * p[1].x + t * t * p[2].x;
y = (1 - t) * (1 - t) * p[0].y + 2 * (1 - t) * t * p[1].y + t * t * p[2].y;

p[0] is the start point, p[1] is the control point, and p[2] is the end point. t is the parameter, which goes from 0 to 1.

Paolo Forgia
  • 6,572
  • 8
  • 46
  • 58
xan
  • 7,511
  • 2
  • 32
  • 45
  • 4
    Multiplying (adding) points in this case, means that you multiply (add) each component. That is, `3 P = [3 * P.x, 3 * p.y]` and `P1 + P2 = [P1.x + P2.x, P1.y + P2.y]`. Finally, to square something, you multiply it with itself: x² = `x * x`. The last part, "t ∈ [1,0]", means that *t* is supposed to be between 0 and 1. – Markus Jarderot Apr 12 '11 at 12:38
  • 8
    So, this means: Point.x = (1-t)^2 * P0.x + 2 * (1-t) * t * P1.x + t^2 * P2.x; Point.y = (1-t)^2 * P0.y + 2 * (1-t) * t * P1.y + t^2 * P2.y; Tested and it works! =) Thank you! – Christian Engel Apr 12 '11 at 13:13
  • 4
    @xan: IMO you should have answered with some code (or pseudo-code) rather than mathematical notation since this is a programming question. –  Dec 26 '13 at 23:01
  • 1
    what is t? What is p0, p1 and p2? – openfrog Jan 05 '14 at 17:12
  • 4
    @openfrog, t gives a fraction of where the point will be with respect to the starting and end point. It's the percentage of where it is assuming that the start and end point total 1, so t is usually a fraction. p0 is your starting point. p1 is your control/anchor point. p2 is your end point. – Razgriz Jan 06 '15 at 04:19
  • @Razgriz that was the crucial missing part in the answer, thanks. – mojuba Jul 29 '15 at 17:01
37

In case somebody needs the cubic form:

        //B(t) = (1-t)**3 p0 + 3(1 - t)**2 t P1 + 3(1-t)t**2 P2 + t**3 P3

        x = (1-t)*(1-t)*(1-t)*p0x + 3*(1-t)*(1-t)*t*p1x + 3*(1-t)*t*t*p2x + t*t*t*p3x;
        y = (1-t)*(1-t)*(1-t)*p0y + 3*(1-t)*(1-t)*t*p1y + 3*(1-t)*t*t*p2y + t*t*t*p3y;
Tatarize
  • 10,238
  • 4
  • 58
  • 64
10

I created this demo :

// x = a * (1-t)³ + b * 3 * (1-t)²t + c * 3 * (1-t)t² + d * t³
//------------------------------------------------------------
// x = a - 3at + 3at² - at³ 
//       + 3bt - 6bt² + 3bt³
//             + 3ct² - 3ct³
//                    + dt³
//--------------------------------
// x = - at³  + 3bt³ - 3ct³ + dt³
//     + 3at² - 6bt² + 3ct²
//     - 3at + 3bt
//     + a
//--------------------------------
// 0 = t³ (-a+3b-3c+d) +  => A
//     t² (3a-6b+3c)   +  => B
//     t  (-3a+3b)     +  => c
//     a - x              => D
//--------------------------------

var A = d - 3*c + 3*b - a,
    B = 3*c - 6*b + 3*a,
    C = 3*b - 3*a,
    D = a-x;

// So we need to solve At³ + Bt² + Ct + D = 0 

Full example here

may help someone.

talkhabi
  • 2,669
  • 3
  • 20
  • 29
  • Your JSFiddle example doesn't actually show y for x. But I tried it anyway. And it worked Converted to swift: https://gist.github.com/eonist/f5bb11533ee52ce24bad3ee47044239a THX! – Sentry.co Mar 18 '17 at 16:34
  • @GitSyncApp its because the `cubic` function. it returns 3 answers that I used just first answer. see http://www.1728.org/cubic.htm – talkhabi Mar 24 '17 at 11:34
  • Yeh, I know. But that was what I needed. Finding y for x on a cubic bezier graph. My point was that your fiddle is sort of scaled in the x-axis. Could be a browser thing ¯\_(ツ)_/¯ It's awesome non the less. Kudos! – Sentry.co Mar 24 '17 at 16:43
2

I edited talkhabis answer (cubic curve) so the curve is displayed with the right coordinates. (Couldn't comment) The Y-coordinates needed to be changed (-p[].y+150). (A new variable for that might be a nicer and more efficient solution, but you get the idea)

// Apply points to SVG and create the curve and controllers :

var path  =  document.getElementById('path'),
    ctrl1 =  document.getElementById('ctrl1'),
    ctrl2 =  document.getElementById('ctrl2'),
    D = 'M ' + p0.x + ' ' + (-p0.y+150) +
    'C ' + c0.x + ' ' + (-c0.y+150) +', ' + c1.x + ' ' + (-c1.y+150) + ', ' + p1.x + ' ' + (-p1.y+150);

path.setAttribute('d',D);
ctrl1.setAttribute('d','M'+p0.x+','+(-p0.y+150)+'L'+c0.x+','+(-c0.y+150));
ctrl2.setAttribute('d','M'+p1.x+','+(-p1.y+150)+'L'+c1.x+','+(-c1.y+150));

// Lets test the "Bezier Function" 

var t = 0, point = document.getElementById('point');

setInterval(function(){

  var p = Bezier(p0,c0,c1,p1,t);
  point.setAttribute('cx',p.x);
  point.setAttribute('cy',-p.y+150);

  t += 0.01;
  if(t>=1) t=0;

},50);


// OK ... Now tring to get "y" on cruve based on mouse "x" : 

var svg = document.getElementById('svg'),
    point2 = document.getElementById('point2');

svg.onmousemove = function(e){

    var x = (e.pageX - 50)/2,  
        y = (e.pageY - 50)/2;
   // "-50" because of "50px margin" on the left side 
   // and "/2" because the svg width is 300 units and 600 px => 300 = 600/2    

  // Get the x,y by mouse x
  var p = YBX(p0,c0,c1,p1,x); 

  point2.setAttribute('cx',p.x);
  point2.setAttribute('cy',-p.y+150);  
} 

http://jsfiddle.net/u214gco8/1/

I also created some C-Code to test the results for the cubic curve. Just enter the X and Y coordinates in the main function.

#include <stdio.h>
#include <stdlib.h> 
#include <math.h> 

void bezierCurve(int x[] , int y[]) 
{ 
    double xu = 0.0 , yu = 0.0 , u = 0.0 ; 
    int i = 0 ; 
    for(u = 0.0 ; u <= 1.0 ; u += 0.05) 
    { 
        xu = pow(1-u,3)*x[0]+3*u*pow(1-u,2)*x[1]+3*pow(u,2)*(1-u)*x[2] 
             +pow(u,3)*x[3]; 
        yu = pow(1-u,3)*y[0]+3*u*pow(1-u,2)*y[1]+3*pow(u,2)*(1-u)*y[2] 
            +pow(u,3)*y[3]; 
        printf("X: %i   Y: %i \n" , (int)xu , (int)yu) ; 
    } 
} 

int main(void) {
    int x[] = {0,75,50,300};
    int y[] = {0,2,140,100};
    bezierCurve(x,y);
    return 0;
}

https://ideone.com/glLXcB

Scm2017
  • 21
  • 1
  • Where is the reasoning behind adjusting the Y with 150? Is this a "fixed" adjustment, or does it vary somehow in different sizes of the curve/container? – phun-ky Mar 16 '21 at 12:02
1

Just a note: If you are using the usual formulas presented here then don't expect t = 0.5 to return the point at half of the curve's length.. In most cases it won't.

More on this here under "§23 — Tracing a curve at fixed distance intervals" and here.

Community
  • 1
  • 1
A.J.Bauer
  • 2,803
  • 1
  • 26
  • 35
  • The curve's length is really hard to measure anyway. At t=0.5 you will on average if you assume random control points be at the center. But, do note that it has the same issue as most curves of varying speed. Finding the halfway point generally would requiring measuring parts of the curve and finding the center bit with a binary search. It's not exactly super typically needed. But, it is worthwhile to understand that if you find all points points at t=.1 increments they will not be equal in length. -- Though this has little to do with question and plenty to do with the nature of the curves. – Tatarize Sep 12 '16 at 23:04
  • 1
    @Tatarize: Mostly true, as also explained in the links provided. A very typical scenario would be a camera or a mesh movement along a path with constant speed... one will probably end up using polylines calculated from curves and using binary search... – A.J.Bauer Sep 13 '16 at 04:27