Spline curves (in 3D)

Written by Paul Bourke
November 1996


Spline curves originate from flexible strips used to create smooth curves in traditional drafting applications. Much like Bezier curves they are formed mathematically from piecewise approximations of cubic polynomial functions with zero, first and second order continuity.

B-Splines are one type of spline that are perhaps the most popular in computer graphics applications, they are defined as follows:
If we have N+1 control points Pk we can derive a continuous function P(v) as

where N(v) are called blending functions, uk are known as break points, where they occur on the curve are known as knots. There are a number of possible options for the knot positions, for example a uniform spacing where uk = k. More commonly the following function is chosen

The blending functions are defined as

Such curves have many advantages

  • Changes to a control point only affects the curve in that locality
  • Any number of points can be added without increasing the degree of the polynomial.
  • As with Bezier curves adding multiple points at or near a single position draws the curve towards that position.
  • Closed curves can be created by making the first and last points the same, although continuity will not be maintained automatically.
  • B-Splines lie in the convex hull of the control points.

    There is another curve in the family called Beta-Splines which have additional parameters beta1 and beta2 which adjust the shape relative to the convex hull. B-Splines are subsets of Beta-Splines.

    The following example (created with the source below) shows the spline through 4 control points, with degree t = 2, 3, and 4. t = 2 is just linear interpolation, as the degree increases the smoother the curve becomes.

    The green line shows the polygon formed by the control points, the white line is the spline curve. Note that the curve lies within the convex hull of the points. Note also that the tangent of the curve at the endpoints is the same as the slope of the line between the first two or last two control points. This makes for convenient matching of the curve between sections. For example in the following the spline on the left has control points starting and ending at the origin. The curve on the right has control points on the same square but the start and end control point is along the bottom edge and the slope of the first two and last two control points is the same.

    Unfortunately the curve always passes through the first and last point. The following has the same control points as for the curve on the right above but for increasing values of the degree, t.

    Splines of degree 3 are by far the most commonly used in practice. Except for the first edge and last edge of the control point polygon these splines touch the midpoint of each other edge. If the control point polygon is concave the spline passes through the midpoint, if the polygon is concave the spline is tangential at the edge midpoint.


    C Source Code

    In what follows it will assumed there are n + 1 control points, Pk where k ranges from 0 to n. The parameter t determines the order of the polynomials used for the blending function, typically 3 or 4.
    The routines presented here are for 3 dimensions but can be readily extended into higher dimensions.

    Following are three functions which implement spline curves, at the end there is a small demonstration program which calls these routines to create the image shown earlier.

    /*
       This returns the point "output" on the spline curve.
       The parameter "v" indicates the position, it ranges from 0 to n-t+2
       
    */
    void SplinePoint(u,n,t,v,control,output)
    int *u,n,t;
    double v;
    XYZ *control,*output;
    {
       int k;
       double b;
    
       output->x = 0;
       output->y = 0;
       output->z = 0;
    
       for (k=0;k<=n;k++) {
          b = SplineBlend(k,t,u,v);
          output->x += control[k].x * b;
          output->y += control[k].y * b;
          output->z += control[k].z * b;
       }
    }
    
    /*
       Calculate the blending value, this is done recursively.
       
       If the numerator and denominator are 0 the expression is 0.
       If the deonimator is 0 the expression is 0
    */
    double SplineBlend(k,t,u,v)
    int k,t,*u;
    double v;
    {
       double value;
    
       if (t == 1) {
          if ((u[k] <= v) && (v < u[k+1]))
             value = 1;
          else
             value = 0;
       } else {
          if ((u[k+t-1] == u[k]) && (u[k+t] == u[k+1]))
             value = 0;
          else if (u[k+t-1] == u[k]) 
             value = (u[k+t] - v) / (u[k+t] - u[k+1]) * SplineBlend(k+1,t-1,u,v);
          else if (u[k+t] == u[k+1])
             value = (v - u[k]) / (u[k+t-1] - u[k]) * SplineBlend(k,t-1,u,v);
         else
             value = (v - u[k]) / (u[k+t-1] - u[k]) * SplineBlend(k,t-1,u,v) + 
                     (u[k+t] - v) / (u[k+t] - u[k+1]) * SplineBlend(k+1,t-1,u,v);
       }
       return(value);
    }
    
    /*
       The positions of the subintervals of v and breakpoints, the position
       on the curve are called knots. Breakpoints can be uniformly defined
       by setting u[j] = j, a more useful series of breakpoints are defined
       by the function below. This set of breakpoints localises changes to
       the vicinity of the control point being modified.
    */
    void SplineKnots(u,n,t)
    int *u,n,t;
    {
       int j;
    
       for (j=0;j<=n+t;j++) {
          if (j < t)
             u[j] = 0;
          else if (j <= n)
             u[j] = j - t + 1;
          else if (j > n)
             u[j] = n - t + 2;	
       }
    }
    
    /*-------------------------------------------------------------------------
       Create all the points along a spline curve
       Control points "inp", "n" of them.
       Knots "knots", degree "t".
       Ouput curve "outp", "res" of them.
    */
    void SplineCurve(inp,n,knots,t,outp,res)
    XYZ *inp;
    int n;
    int *knots;
    int t;
    XYZ *outp;
    int res;
    {
       int i;
       double interval,increment;
    
       interval = 0;
       increment = (n - t + 2) / (double)(res - 1);
       for (i=0;i<res-1;i++) {
          SplinePoint(knots,n,t,interval,inp,&(outp[i]));
          interval += increment;
       }
       outp[res-1] = inp[n];
    }
    
    /*
       Example of how to call the spline functions
    	Basically one needs to create the control points, then compute
       the knot positions, then calculate points along the curve.
    */
    #define N 3
    XYZ inp[N+1] = {0.0,0.0,0.0,   1.0,0.0,3.0,   2.0,0.0,1.0,   4.0,0.0,4.0};
    #define T 3
    int knots[N+T+1];
    #define RESOLUTION 200
    XYZ outp[RESOLUTION];
    
    int main(argc,argv)
    int argc;
    char **argv;
    {
       int i;
    
       SplineKnots(knots,N,T);
       SplineCurve(inp,N,knots,T,outp,RESOLUTION);
    
       /* Display the curve, in this case in OOGL format for GeomView */
       printf("LIST\n");
       printf("{ = SKEL\n");
       printf("%d %d\n",RESOLUTION,RESOLUTION-1);
       for (i=0;i<RESOLUTION;i++)
          printf("%g %g %g\n",outp[i].x,outp[i].y,outp[i].z);
       for (i=0;i<RESOLUTION-1;i++)
          printf("2 %d %d 1 1 1 1\n",i,i+1);
       printf("}\n");
    
       /* The axes */
       printf("{ = SKEL 3 2  0 0 4  0 0 0  4 0 0  2 0 1 0 0 1 1 2 1 2 0 0 1 1 }\n");
    
       /* Control point polygon */
       printf("{ = SKEL\n");
       printf("%d %d\n",N+1,N);
       for (i=0;i<=N;i++)
          printf("%g %g %g\n",inp[i].x,inp[i].y,inp[i].z);
       for(i=0;i<N;i++)
          printf("2 %d %d 0 1 0 1\n",i,i+1);
       printf("}\n");
    
    }