- Published on
Bezier Basics
History
If you've ever done graphic design, you've probably used bezier curves without knowing it. "Beh-zee-egh". Sounds french, right? Well, it is. French engineer Pierre Bezier designed it while working in the company 'Renault'.
Curves
One's idea of 'curves' is either a good ol' or 'circular round-y things'. But curves could be parametrically altercated to form loops and weird stuff.
What counts as a curve (A bezier curve)? A curve with 3 or more points- Start, end and all the other control points. More number of control points means higher precision.
All of a sudden, we have a computationally efficient and elegant way to define and interact with curves and graphics. After that, we can do crazy stuff like have coordinate spaces and wacky 'local geometries' for points that let developers do stuff like procedural generation and stuff. (Out of the scope of this section.)
Why
Curves and Paths form the very backbone of modern graphics, with applications ranging from video game models to digital animation, to web design and more.
Most of those who say they've 'given the design a shot but found that it just isn't for them' have probably installed photoshop or Inkscape clicked on the pen tool and tried to figure it out, only to get confused and quit, never to return again.
Anybody who cares about any sort of computer graphics-related domain should care about bezier curves. So, vamos.
insert profound phrase here
Linear Interpolation
This is a method of curve fitting that uses linear polynomials to construct new data points within the range of a discrete measurable set of known data points. It is a form of approximation.
Let's consider two distinct points and , and another point that's located between them. Here, all these points are the control points.
We can define the position of with a value between and named that is somewhat like a percentage:
- if , will move to
- if , will move to
- Values between and would be between of and
This functionality is called 'Linear Interpolation' or ✨lerp✨ in short. Mathematically, you can express this idea as:
Let's add another point! We can now have two interpolated points, between pairwise segments, moving respectively on the axis and . Linking these two points with a segment and positioning an interpolated point on this line as well gives us something rather interesting: the dot follows a specific path. It resembles a curve. This specifc curve is called a Quadratic Bézier curve.
Similarly, we get a Cubic Bezier Curve too! And that might sound familiar to frontend developers (or really anyone who has bashed their own head in trying to make some CSS flow just right) as cubic beziers are extensively used for animation pacing, normally called 'easing'.
De Casteljau's Algorithm
The polynomials as we've seen were not applied to graphics until a 50-odd-years after their inception when mathematician Paul de Casteljau developed his algorithm, and he became the first to apply them to computer-aided design. This algorithm was numerically stable. This basically refers to the property of algorithms, that, errors in the input become less and less significant as the algorithm executes, having a minimal effect on the final output.
As we see here, increasing the order of the bezier curve is simply equivalent to adding more and more nested lerps! This is a geometric approach to curve drawing, and it's really easy to implement. So easy, in fact, one can do it by hand with a pencil and ruler.
Steps
- Treat as a ratio (which it is). is percent along a line, is percent along a line.
- Take all lines between the curve's defining points. For an order curve, that's lines.
- Place markers along each of these lines, at distance . So if is , place the mark at % from the start, % from the end.
- Now form lines between those points. This gives lines.
- Place markers along each of these lines at distance .
- Form lines between those points. That'll be lines.
- Place markers, form lines, place markers, etc.
- Repeat this until you have only one line left. The point on that line coincides with the original curve point at
Pseudocode
function drawCurvePoint(points[], t):
if(points.length==1):
draw(points[0])
else:
newpoints=array(points.size-1)
for(i=0; i<newpoints.length; i++):
x = (1-t) * points[i].x + t * points[i+1].x
y = (1-t) * points[i].y + t * points[i+1].y
newpoints[i] = new point(x,y)
drawCurvePoint(newpoints, t)
All Good Things Must Be Mathified
At the school level, polynomials look like . Now, in a similar vein, since we know that curves are just lerps all the way down. Yes, we're just casually using lerps now.
Code to generate such polynomials:
def Bezier(n,t):
sum = 0
for(k=0; k<n; k++):
sum += n!/(k!*(n-k)!) * (1-t)^(n-k) * t^(k)
return sum
But we can do better! (By having a lookup table instead of doing expensive calculations all the time)
lut = [ [1], #n=0
[1,1], #n=1
[1,2,1], #n=2
[1,3,3,1], #n=3
[1,4,6,4,1], #n=4
[1,5,10,10,5,1], #n=5
[1,6,15,20,15,6,1] #n=6
]
# these are just pascal's triangles!
def binomial(n,k):
while(n >= lut.length):
s = lut.length
nextRow = new array(size=s+1)
nextRow[0] = 1
for(i=1, prev=s-1; i<s; i++):
nextRow[i] = lut[prev][i-1] + lut[prev][i]
nextRow[s] = 1
lut.add(nextRow)
return lut[n][k]
General Formula
Now, of course, what makes bezier curves so incredible is that they can be 'controlled' and manipulated to produce all kinds of cool art and graphics!
Being interpolation functions, curves take a set of points and generate values somewhere "between" those points. One of the consequences of this is that you'll never be able to generate a point that lies outside the outline for the control points, commonly called the "hull" for the curve. In general, an n-order curve has n-1 control points. (Picture this. A Quadratic polynomial curve has one turning point. Cubic has two, and so on.)
And the neat thing is, we can control the final output of the Bezier curve by controlling how each point contributes to the value generated by the function. If we want to change the curve, we need to change the weights of each point, effectively changing the interpolations. The way to do this is about as straightforward as possible: just multiply each point with a value that changes its strength. These values are conventionally called "weights", and we can add them to our original Bézier function:
def Bezier(n,t,w[]):
sum = 0
for(k=0; k<=n; k++):
sum += w[k] * binomial(n,k) * (1-t)^(n-k) * t^(k)
return sum
The GUI analogue is 'handles' . Manipulating the 'handles' of a path in say Inkscape or Photoshop lets us control the trajectory of the path.
Matrices (because why not.)
We can represent Bézier curves as matrix operations, by expressing the Bézier formula as a polynomial basis function and a coefficients matrix, and the coordinates as the matrix.
Let's take the example of a cubic curve. Using ... to refer to coordinate values "in one or more dimensions"
We can also represent Bézier curves as matrix operations, by expressing the Bézier formula as a polynomial basis function and a coefficients matrix, and the actual coordinates as a matrix.
Disregarding our actual coordinates for a moment, we can write this as a sum of four terms (Above equation but without the P's)
Expanding, we get the following.
Now, this can be visualized as four matrix operations.
Now, this isn't the exact same logic, but besides being computationally nice to work with, a matrix representation allows us to extend the idea to path operations too!
Here is an example of applying the 'skew' and 'rotate' operations to make text appear as if it were projected onto the surface of a cube
Sources
'An Ode To Bezier Curves' by Takashi Wickes
'A Primer on Bezier Curves' by Pomax
'From Math To Motion' by Maxime Heckel
'The Beauty of Bezier Curves' by Freya Holmer