0

I am have a set of control points, and I am trying to draw a cubic B-spline (degree 3) based on these control points. The issue I'm experiencing is that my curve does not connect to the final control point, but instead it draws the curve to some other point that is in a different area all together. The curve points approach (0, 0) after a certain time.

Image of just the control points

Image of the curve and the control points. Note that the curve correctly starts at the first control point but does not end at the last control point.

The code I'm working with:

float Stroke::calculate_N(float t, int i, int j, vector<float> knots){
    float t_1 = knots[i];
    float t_2 = knots[(i + j)];
    float t_3 = knots[(i + 1)];
    float t_4 = knots[(i + j + 1)];

    // Base case of basis function
    if (j == 0){
        if (t_1 <= t && t < t_3) return 1;
        else return 0;
    }

    float temp1 = (t_2 - t_1 == 0) ? 0 : ((t - t_1) / (t_2 - t_1)) * calculate_N(t, i, j-1, knots);
    float temp2 = (t_4 - t_3 == 0) ? 0 : ((t_4 - t) / (t_4 - t_3)) * calculate_N(t, i+1, j-1, knots);

    return temp1 + temp2;
}

vector<float> make_knot_vector(int m, int p, int n){
    vector<float> knots;
    for (int i = 0; i <= p; i++){
        knots.push_back(0.0);
    }
    for (int i = 1; i <= n - p; i++){
        knots.push_back((float)i/(float)(n-p+1));
    }
    for (int i = 0; i <= p; i++){
        knots.push_back(1.0);
    }
    return knots;
}

int main(){
    // Init control points
    s = Spline();
    s.add_control_point(100,100);
    s.add_control_point(232,71);
    s.add_control_point(148,294);
    s.add_control_point(310,115);
    s.add_control_point(375,280);

    // Get the number of knots based on the number of control points and degree
    int num_ctrl_pts = s.get_control_points().size();
    float NUM_KNOTS = (float)(num_ctrl_pts + 3 + 1);

    // Draw each control point in red
    for (auto pt : s.get_control_points()){
        int x = pt->get_x();
        int y = pt->get_y();
        int r = s.get_radius();

        vector<vector<float>> circle_points = calc_circ(y, x, r);
        int si = circle_points.size();
        for (auto circ_point : circle_points){
            c->setColor(circ_point[0], circ_point[1], Color(1.0, 0.0, 0.0));
        }
    }

    // Draw the curve
    vector<float> knots = make_knot_vector(NUM_KNOTS, 3, num_ctrl_pts);
    for (float t = 0.0; t < 1.0; t+= 1.0/1000.0){
        Vector sum = Vector(0.0, 0.0);

        for (int i = 0;i < num_ctrl_pts; i++){
            Vector next = *(s.get_control_points()[i]);
            float n = s.calculate_N(t, i, 3, knots);
            next = next * n;
            sum = sum + next;
        }

        cout<<"("<<(int)sum.get_x()<<", "<<(int)sum.get_y()<<")"<<endl;

        // Draw the curve point in green
        vector<vector<float>> circle_points = calc_circ((int)sum.get_y(), (int)sum.get_x(), s.get_radius());
        for (auto circ_point : circle_points){
            c->setColor(circ_point[0], circ_point[1], Color(0.0, 1.0, 0.0));
        }
    }

    c->writeImage(path + "spline.ppm");

    // delete canvas;
    return 0;
}
jim bob
  • 1
  • 1

1 Answers1

0

A cubic B-spline consists of a start point (knot), 2 control points and an end point (knot). The curve does not pass through its control points, only through its knots.

When combining multiple cubic B-splines into a shape, the end point of one spline is usually the start point of the next to avoid gaps. For the curve to look smooth, this knot and the neighbouring control points need to be co-linear (all three on the same line).

You should check whether the Spline class makes a distinction between control points and knots. If not, chances are that it simply expects every 3rd point (starting at the first and then skipping two) to be a knot. In that case, make sure to add at least 4 points, or 7, or 10, etc.

If Spline is only accepting knots (in which case you should rename the add_control_point() member function to add_knot()), it can calculate its control points automatically. A common way to do this is by constructing a Catmull-Rom spline. The spline will go through points 2, 3, 4 ... n-1. To add the first (from 1 to 2) and last segment (from n-1 to n), you normally add the first and last points twice.

// Pseudo code: auto spline = CatmullRom( { p1, p1, p2, p3, p4, p5, p5 } );

A better solution is to reflect the 2nd point around the 1st:

auto p0 = 2 * p1 - p2; auto p6 = 2 * p5 - p4; auto spline = CatmullRom( { p0, p1, p2, p3, p4, p5, p6 } );

JaMiT
  • 14,422
  • 4
  • 15
  • 31
Paul Houx
  • 1,984
  • 1
  • 14
  • 16
  • I think my error comes from a misunderstanding of knots. How are knots calculated? I believe that knots are not the same as control points, so in my `Spline` class there is a distinction between the two. However, I don't think I calculate the knots anywhere. I do create a knot vector (which I don't really understand the purpose of), but I don't think I explicitly calculate just the knots of the curve, which is possibly where my error arises? – jim bob Nov 30 '18 at 21:15
  • It could be that "knots" and "control points" can be used interchangeably and there is no real distinction between them. But I usually do make that distinction: the curve will pass through the knots, but not the control points. It will only turn into the direction of the control points. See [this link](http://nurbscalculator.in/) to play with the control points to get a better understanding. You provide the knots yourself, randomly if need be. The control points can either be provided as well, or calculated automatically from the knots. – Paul Houx Nov 30 '18 at 21:29
  • 1
    Thank you so much!! It seems that my understanding was correct, but I was creating an extra value in my knot vector (<= instead of <). The link you sent was extremely helpful. Thank you again! – jim bob Nov 30 '18 at 21:34
  • Actually, after playing with that tool in the link, I think they calculate the "knots" automatically (exactly halfway between two control points), adding to the confusion. But I'm glad you figured it out. – Paul Houx Nov 30 '18 at 21:37
  • Continuity is implied by having the end point of one spline be the start point of the next, isn't it? Having the neighbouring control points co-linear with the knot would make the curve smooth/differentiable. – JaMiT Nov 30 '18 at 21:56
  • As with knots and control points, I am not entirely sure what the *official* definition of continuity of a curve is, so you may have a valid point, JaMiT. My definition of continuity is that the curve indeed goes smoothly through the knot, as if there were no knot at all. And this requires that the control points on each side of the knot lie on a line through the knot. If they don't, you will see a sharp corner. – Paul Houx Dec 01 '18 at 14:21
  • The mathematical definition of continuity involves a lack of gaps. A common informal version of the definition is "you can draw it without taking your pen off the paper". A sharp corner is continuous (your pen does not have to leave the paper) but not differentiable (your pen makes an abrupt change in direction). – JaMiT Dec 01 '18 at 16:31