2
\$\begingroup\$

I'm trying to draw a bezier curve using 3 points. If I use the quadratic form:

enter image description here

I get this result:

enter image description here

And I believe it's correct.

Now since I need to draw it with a variable number of points, I'm trying to apply the explicit definition:

enter image description here

This is the code that implements it:

typedef struct Point2D
{
    GLfloat x;
    GLfloat y;
    Point2D operator+(GLfloat c) const 
    {
        return {x+c, y+c};
    }
    Point2D operator+ (const Point2D& other) const 
    {
        return {x+other.x, y+other.y};
    }
    void operator+= (GLfloat c)
    {
        y+=c;
        x+=c;
    }
    void operator+= (const Point2D& other)
    {
        x+= other.x;
        y+= other.y;
    }
    Point2D operator* (GLfloat factor) const 
    {
        return {x*factor, y*factor};
    }
    void operator*= (GLfloat factor)
    {
        x*=factor;
        y*= factor;
    }
}Point2D;

Point2D bezier(const vector<Point2D>& points, GLfloat u) // u is t
{
    Point2D point;
    unsigned int n= points.size();
    vector<int> factorials(n);
    // For efficiency, I put all factorials in an array instead
    // of recalculating them each time.
    GLfloat c1= pow(1.0-u,n-2); 
    // This is (1-t)^(n-1)
    GLfloat c2= 1.0;
    // This is t^i , I'll progressively multiply it for u.
    factorials[0]=1;
    for(unsigned int i=1; i<n;i++)
    {
        factorials[i]= factorials[i-1]*i;
    }
    point= points[0] * (GLfloat)(n-1) * c1; // This is the first point
    for(unsigned int i=1; i<n; i++)
    {
        c2*= u;
        GLfloat bCoeff= factorials[n-1]/(factorials[i]*factorials[n-2]);
            // bCoeff is the binomial coefficient: n! / ( i! * (n-1)! )
        point+= points[i] * bCoeff * c1 * c2;
    }
    return point;
}



void display()
{
    glClearColor(0.75,0.75,0.75,1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    glColor3f(1.0,0.0,0.0);
    glBegin(GL_POINTS);
    vector<Point2D> points{Point2D{200,200}, Point2D{400,400}, Point2D{600,200} };
    for(GLfloat u=0.0; u<=1.0; u+=1.0e-4)
    {
        Point2D point;
        point= bezier(points,u);
        glVertex2f(point.x,point.y); // For now I use the immediate mode
                                         // I'll put it in a vbo.
    }
    glEnd();

    glutSwapBuffers();
}

Unfortunately this is the result that I get:

enter image description here

Which I believe is incorrect. I guess I'm not applying correctly the explicit definition, but I don't know where's the mistake, I would like to know what I'm doing wrong.

\$\endgroup\$

2 Answers 2

7
\$\begingroup\$

It is indeed wrong for at least two reasons:

  • you never update c1.
  • you use n! / ( i! * (n-1)! instead of n! / ( i! * (n-i)!.

This means you'll need to reorganise your code. You might try something like this:

unsigned int n = points.size();

vector<float> v;
for (unsigned i = 0; i < n; i++)
    v.push_back(1.f);

/* (un1) is t^i, (un2) is (1-t)^i */
float un1 = 1.f, un2 = 1.f, inv_fact = 1.f;

/* At the end of the loop, we are still missing the
 * "n!" multiplication, but we can do it at the very
 * end instead. */
for (unsigned i = 0; i < n; i++)
{
    v[i] *= un1 * inv_fact;
    un1 *= u;
    v[n - 1 - i] *= un2 * inv_fact;
    un2 *= (1.f - u);
    if (i != 0 && i != n - 1) inv_fact /= i;
}

Point2D point{0,0};
for (unsigned i = 0; i < n; i++)
    point += v[i] * points[i];

return (1.f / inv_fact) * point;
\$\endgroup\$
0
3
\$\begingroup\$

You could also use my recursive approach to n-order bezier curves. This method can also be adapted for use with integers if needed.

class bezier{
    std::vector<Point2D> points;
public:
    bezier();
    void PushPoint2D( Point2D point );
    Point2D GetPoint( double time );
};

bezier::bezier(){}
void bezier::PushPoint2D( Point2D point ){
    points.push_back(point);
}
Point2D bezier::GetPoint( double time ){
    Point2D p;
    if( points.size() == 1 ) return points[0];
    if( points.size() == 0 ) return p;

    bezier b;
    for( int i = 0; i < points.size()-1; ++i ){
        p.x = ( points[i+1].x - points[i].x ) * time + points[i].x;
        p.y = ( points[i+1].y - points[i].y ) * time + points[i].y;
        if( points.size() <= 2 ) return p;
        b.PushPoint2D(p);
    }
    return b.GetPoint( time );
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .