Notes for Tuesday April 3:
Cubic Splines

 

The word "spline" had its origin in ship building. To create smooth hull shapes, ancient ship builders would drive pegs into the ground, and then lay down a very large thin flexible strip of wood (the "spline") that would be forced into a curve by the position of the pegs.

They would then use the resulting smooth curve as a guide for cutting the lumber that they would use to create the hulls of their ships.

In more recent times, beginning in the 1960s, designers in the automotive and aerospace industries started using computer software to generate smooth curves when designing their vehicles. The math they used for this was pretty much the same as what we covered today in class.

In class I showed an example of using the same smooth spline both to create the profile of a object, and also as an animation path. As you can see in the screen capture to the right, the same spline is being used to create the shape of the earthen flask and to specify the path over time of the swimming fish.

PARAMETRIC CUBIC CURVES

The underlying math for creating smooth splines is generally to create a set of parametric cubic curves that fit together seamlessly, so that they appear to form a single smooth curve. The general recipe is as follows:

  • Divide the curve into simple curved segments.

  • The position and orientation at the end of each segment is the same as the position and orientation at the beginning of the next segment.

  • The points that begin and end segments are called "key points".

  • Each segment is a parametric curve x(t), y(t), where t varies between 0.0 and 1.0.

  • In particular, the position along each dimension of every segment is a cubic function:

    x(t) = ax * t3 + bx * t2 + cx * t + dx
    y(t) = ay * t3 + by * t2 + cy * t + dy

After defining the above functions x(t) and y(t), it's easy to draw the resulting spline segment. For example:

   function drawSpline(x, y, dt) {
      let a = [x(0), y(0)];
      for (let t = dt ; t <= 1 ; t += dt) {
         let b = [x(t), y(t)];
         drawLine(a, b); // we assume this function is already defined. **
         a = b;
      }
   }

** To be clear (because some students have asked), you don't need to create a drawLine function. I'm just using it here as an example.

TRANSLATING FROM THE HUMAN DESIGNER TO THE COMPUTER

For most human beings, it would be extremely difficult to design such curves by directly typing the coefficients of cubic polynomials. For this reason, we create other ways of defining those coefficients, which are more human-friendly.

All such methods work by transforming some easier to understand set of values into the underlying cubic coefficients (a,b,c,d). In class we went over two of the more important such methods, Hermite splines and Bezier splines.

HERMITE SPLINES

If our human user wants to define things in terms of the position and orientation at the beginning and end of each cubic spline segment, then we use the Hermite spline.

We do all the math independently for each coordinate (eg: x and y, or x,y and z).

P0 = value at start of the segment (where t = 0).
P1 = value at end of the segment (where t = 1).
R0 = slope at start of the segment (where t = 0).
R1 = slope at end of the segment (where t = 1).

We create four "basis functions", each of which varies only one thing:

only P0: 2t3 - 3t2 + 1
only P1:-2t3 + 3t2
only R0:  t3 - 2t2 + t
only R1:  t3 - t2

So to get from (P0,P1,R0,R1) to the coefficients (a,b,c,d) that define cubic polynomial a * t^3 + b * t^2 + c * t + d, we apply the Hermite Matrix, which is just a way of describing these four basis functions. Each of the four functions is described in a single column of the Hermite Matrix:

a
b
c
d
 ⇐  2
-3
0
1
-2
3
0
0
1
-2
1
0
1
-1
0
0
 ×  P0
P1
R0
R1

BEZIER SPLINES

If our human user wants to define things by moving points around on a screen, then the Bezier spline is a good choice.

Again, we do the math independently for each coordinate.

A = value at start of the segment (where t = 0).
B = value at a first "guide point".
C = value at a second "guide point".
D = value at end of the segment (where t = 1).

The math is basically successive linear interpolations:

   mix (
      mix ( mix(A,B,t) , mix(B,C,t) , t ),
      mix ( mix(B,C,t) , mix(C,D,t) , t ),
      t
   )

where we define mix(a,b,t) as linear interpolation:

(a,b,t) ⇒ (1-t) * a + t * b

In other words:

   (1-t) * ( (1-t) * ((1-t)*A + t*B)  +  t * ((1-t)*B + t*C) )
     +
     t   * ( (1-t) * ((1-t)*B + t*C)  +  t * ((1-t)*C + t*D) )

When you multiply everything out, this turns into:

       (1-t) * (1-t) * (1-t) * A +
   3 * (1-t) * (1-t) *   t   * B +
   3 * (1-t) *   t   *   t   * C +
         t   *   t   *   t   * D

This gives us what we need to go from key values (A,B,C,D) to cubic coefficients (a,b,c,d). Each line of the above equation can be turned into a column of the characteristic Bezier Matrix:

a
b
c
d
 ⇐  -1
3
-3
1
3
-6
3
0
-3
3
0
0
 1
0
0
0
 ×  A
B
C
D

Homework (due before class on Tuesday April 10):

Implement either Hermite or Bezier splines.

For extra credit, implement both.

Apply your spline to either creating shapes. For example, you can use a spline to vary both the radius and the height of a parametric shape, as a function of v, like the vase shape I showed in class.

Or try creating animated movement, using splines to vary rotation and/or translation over time.

For extra credit, do both.