Bezier Curves

ORIGINALLY POSTED NOVEMBER 16, 2011

This week: Bezier curves, how to draw them in Python and particularly, how to decide where to put the control points.

A Bezier curve, is a special type of parametric curve used frequently in computer graphics. You may have seen them in Powerpoint, or using the pen tool in illustrator, but for my purpose, they are a lovely way to visualise flow paths on a map, like this:

This particular visualisation explores the London public transport network and was made by my rather awesome colleague Dr Ed Manley. It shows London's most popular destinations by origin, derived from a large dataset of morning peak Oyster Card trips.

The curves themselves are anchored at both ends (origin P0, destination P3) and get their shape from two control points (blue P1 and red P2), as in the following picture:

 

The curve itself is created from the fairly simple expression (everything in bold is a vector):

latex.png

The parameterisation t starts from zero, so that the curve begins at P0, and as it increases bends the line towards the two control points, first P1 and then P2. Finally, when t  is 1, the line finishes at P3.

It’s all very clever stuff, and there is lots and lots of literature available on the web about them. But – as far as I can see – no one ever tells you how to decide where to put your control points, and certainly doesn’t offer an algorithm to automatically do so.

But fear not people! I have written one. And ugly and fudgy as it is, it does the job.

The code below takes 6 inputs:

-origin points,

-destination points,

-a length for the blue line,

-a length for the red line (both as a fraction of the length of the green line)

-the angle between the blue and green lines

-the angle between the red and green lines (both in radians. I am a mathematician after all).

And it will spit out a totally delicious plot of your curves.

It’s set up to bend the curves clockwise (as in the image at the top of the page), which generally looks nicer when plotting more than one bezier curve.

Have a play with the last four inputs: the longer the length, the more the curve will deviate from the green line. The higher the angle, the steeper the curve will roll in to it’s destination. Indeed, choosing the angles to be on the other side of the green line will result in an s-shaped curve. Anyway – the idea is for you to hack out the bits of the code that are useful.

import scipy as sp
import pylab as plt

#### Inputs

#A list of P0's and P3's. Must be the same length
origins = [[1,0],[-0.5,sp.sqrt(3)/2], [-0.5,-sp.sqrt(3)/2]]
destinations = [[0,0],[0,0],[0,0]]

#The angle the control point will make with the green line
blue_angle = sp.pi/6
red_angle = sp.pi/4

#And the lengths of the lines (as a fraction of the length of the green one)
blue_len = 1./5
red_len = 1./3

### Workings

#Generate the figure
fig = plt.figure()
ax = fig.add_subplot(111)
ax.hold(True)

#Setup the parameterisation
t = sp.linspace(0,1,100)

for i in xrange(len(origins)):
#Read in the origin & destination points
POx,POy = origins[i][0], origins[i][1]
P3x,P3y = destinations[i][0], destinations[i][1]

#Add those to the axes
ax.plot(POx,POy, 'ob')
ax.plot(P3x,P3y, 'or')
ax.plot((POx,P3x),(POy,P3y), 'g')

#Work out r and theta (as if based at P3)
r = ((POx-P3x)**2 + (POy-P3y)**2)**0.5
theta = sp.arctan2((POy-P3y),(POx-P3x))

#Find the relevant angles for the control points
aO =theta + blue_angle+ sp.pi
aD = theta - red_angle

#Work out the control points
P1x, P1y = POx+ blue_len*r*sp.cos(aO), POy + blue_len*r*sp.sin(aO)
P2x, P2y = P3x+ red_len*r*sp.cos(aD), P3y + red_len*r*sp.sin(aD)

#Plot the control points and their vectors
ax.plot((P3x,P2x),(P3y,P2y), 'r')
ax.plot((POx,P1x),(POy,P1y), 'b')
ax.plot(P1x, P1y, 'ob')
ax.plot(P2x, P2y, 'or')

#Use the Bezier formula
Bx = (1-t)**3*POx + 3*(1-t)**2*t*P1x + 3*(1-t)*t**2*P2x + t**3*P3x
By = (1-t)**3*POy + 3*(1-t)**2*t*P1y + 3*(1-t)*t**2*P2y + t**3*P3y

#Plot the Bezier curve
ax.plot(Bx, By, 'k')

#Save it
plt.savefig('totally-awesome-bezier.png')
plt.show()
#Bosch.