At some point over the last few days I looked at a list of maths topics that I have to study next year at university and I thought I might not have such a heavy workload when the time comes if I put some time over the summer into figuring them out. I find the best way to learn maths is to write code that involves it.
This is true of pretty much anything, really. Learning how a system works? Program it. Tinker with it. It’s the difference between looking at diagrams about how to tie your laces and actually practising with your hands.
Bézier curves are a nifty way of representing curves. This website is a fantastic overview of them and their surrounding topics, and I highly recommend it if you want to learn more, because this is just a write-up of an afternoon coding project I undertook recently and isn't going to go into anywhere near as much detail. (Moreover, that site has interactive diagrams and curves, which I don’t know how to do yet. Soon, though. Soon.)
They’re not very intimidating things, really - just polynomials manipulated by a set of ‘control points’. Look:
This is true of pretty much anything, really. Learning how a system works? Program it. Tinker with it. It’s the difference between looking at diagrams about how to tie your laces and actually practising with your hands.
Bézier curves are a nifty way of representing curves. This website is a fantastic overview of them and their surrounding topics, and I highly recommend it if you want to learn more, because this is just a write-up of an afternoon coding project I undertook recently and isn't going to go into anywhere near as much detail. (Moreover, that site has interactive diagrams and curves, which I don’t know how to do yet. Soon, though. Soon.)
They’re not very intimidating things, really - just polynomials manipulated by a set of ‘control points’. Look:
Here is a very simple Bézier curve with only 3 control points: the start (bottom left), the end (top right), and one in between (correct, it is indeed in the bottom right). As you can see, the curve bends towards the middle control point.
All Bézier curves are like this, having a start point, an end point, and bunch of points in between. If you were to draw lines sequentially joining up all the control points, the curve would fit within the shape formed by those lines.
All Bézier curves are like this, having a start point, an end point, and bunch of points in between. If you were to draw lines sequentially joining up all the control points, the curve would fit within the shape formed by those lines.
So how does all this work? How do we get the x and y values of the curve at some distance along the curve?
You know how to do linear interpolation, right? It's basically just that. But not linear. So instead of this:
You know how to do linear interpolation, right? It's basically just that. But not linear. So instead of this:
// 0 <= t <= 1 Vector2f Lerp(Vector2f start, Vector2f end, float t) { return start + (end - start) * t; }
... we throw in more maths, so that in the end we have something looking like this:
Vector2f BezierCurve::GetValueAt(const float t) const { int order = m_control_points.size() - 1; Vector2f sum; for (int i = 0; i <= order; i++) { sum += m_control_points[i] * Binomial(order, i) * pow((1.f - t), (order - i)) * pow(t, i); } return sum; }
(I'd write a nicely-formatted mathematical formula version for this here to make this section clearer if I could figure out a good way to do it with Weebly. Might come back for that.)
Simply put, for each control point, we warp the curve by a certain amount. The 'order' of the curve is equal to its number of control points minus one. A curve with only 2 control points isn't much of a curve at all - it's a straight line, of order 1, it is linear. With 3 control points we get an order 2 (quadratic) curve. With 4 we get an order 3 (cubic) curve. And so on.
At each iteration, before we add in the control point's position to the sum, we multiply it by the binomial coefficient for the order of the curve and the current term we're on, i. This is done by just looking up the value in Pascal's triangle at (order, i). After that we multiply by the polynomial section of the formula, (1 - t)^(order-i) * t^i, and we're ready to move on to the next iteration.
Simply put, for each control point, we warp the curve by a certain amount. The 'order' of the curve is equal to its number of control points minus one. A curve with only 2 control points isn't much of a curve at all - it's a straight line, of order 1, it is linear. With 3 control points we get an order 2 (quadratic) curve. With 4 we get an order 3 (cubic) curve. And so on.
At each iteration, before we add in the control point's position to the sum, we multiply it by the binomial coefficient for the order of the curve and the current term we're on, i. This is done by just looking up the value in Pascal's triangle at (order, i). After that we multiply by the polynomial section of the formula, (1 - t)^(order-i) * t^i, and we're ready to move on to the next iteration.
To render the curve it is split up into segments, with higher numbers of segments producing smoother curves:
The demo program I wrote lets the user create any 2-dimensional curve they want by adding and rearranging control points.
Controls:
You can download a (hopefully functional) copy of this very simple program below. Enjoy!
Controls:
- Left click: place a new control point. The new point becomes the second point in the curve's list of points.
- Left click and hold over an existing point moves the point around.
- H: hide or reveal the text.
- Up arrow, down arrow: increase or decrease the number of segments.
- R: reset the curve. Removes all the control points.
You can download a (hopefully functional) copy of this very simple program below. Enjoy!
bezier_curves_debug_v1.zip |