Introduction
Recently I made a small demo, where I wanted to specify a subinterval of a Bézier curve and then create a new curve for that subinterval.
The next section shows the algorithm and afterwards you can see the derivation of everything.
It turns out, that it is pretty easy to do that, just requiring a small modification of the common de Casteljau algorithm. At the same time, it is really hard to actually find the complete derivation on why it works. Most sources I have found only show parts of it, where the missing parts are "obvious". Others are very nice in theory, but are a bit more complicated.
One example is the very nice generalization by DeRose . There, a de Casteljau-like algorithm is presented for an arbitrary reparametrization described in Bernstein polynomial form.
Another example is the more abstract technique called blossoming. You can read more about it here . While this is very interesting and can capture a lot of effects, it also requires additional theory and notation.
This document contains all derivations to arrive at the algorithm and just needs the knowledge you would commonly have, when being introduced to Bèzier curves.
Algorithm
Given is a Bézier curve with n+1 control points pi with i=0,…,n.
We want to create the control points for a new curve corresponding to the parameter range [tl,tu] of the original curve.
For that, we will use the well documented de Casteljau algorithm. A quick explanation and code can be found at Wikipedia, but it is very easy to find code in many languages.
Here is a basic JavaScript code snippet:
function deCasteljau(points, t) {
points = [...points];
const n = points.length - 1;
for (let j = 1; j <= n; j++) {
for (let i = 0; i <= n - j; i++) {
points[i] = lerp(points[i], points[i + 1], t);
}
}
return points[0];
}
Here is a description, below is JavaScript pseudo code.
- Split the curve given by the points pi to the left to get the range [0,tu]. For that, insert the first point of the curve into an array. Then run the de Casteljau algorithm with the parameter tu and after each step append the first point to the array. So the last point will be the resulting point for p(tu). We call these new points qi
- Split the resulting subcurve from step (1) defined by the qi to the right to get the range [tl,tu]. For that, insert the last point of the curve into an array. Then run the de Casteljau algorithm with the parameter tutl and after each step append the last point to the array. So the last point will be the resulting point for q(tutl). We call these new points ri. This is the result. NOTE: As there is one point less after each step, the last point at step j has the index n−j, assuming a 0-based index
So splitting a curve comes down to performing the de Casteljau algorithm two times. Here, we will show a JavaScript version of the modified de Casteljau algorithm, that compiles the control points for the left and right splits together. This can of course be optimized, but works.
function subdivideBezierControlPoints(points, t) {
points = [...points];
const left = [points[0]];
const right = [points[points.length - 1]];
const n = points.length - 1;
for (let j = 1; j <= n; j++) {
for (let i = 0; i <= n - j; i++) {
points[i] = lerp(points[i], points[i + 1], t);
}
left.push(points[0]);
right.push(points[n - j]);
}
return { left, right: right.reverse() };
}
The final split code in JavaScript pseudo code:
function subintervalBezierControlPoints(points, tl, tu) {
({ left: points } = subdivideBezierControlPoints(points, tu));
let t = tl / tu;
({ right: points } = subdivideBezierControlPoints(points, t));
return points;
}
Derivation
This section will derive all the necessary parts of the algorithm. The order is generally such, that the later steps use the previous ones.
Here is an overview:
- Show the symmetry of Bernstein polynomials
- Use (1) to show, that the control points of a reversed Bézier curve are the original points in opposite order
- Show that a formula for a scaled parameter in the Bernstein polynomials is valid
- Show the validity of an explicit formula for all the points generated during the de Casteljau algorithm
- Show how we can compute the control points of a Bézier curve that covers the interval [0,tu] of a given curve using (3) and (4)
- Use (2) and (5) to show how to compute the control points of a Bézier curve that covers the interval [tl,1]
- When we first split a curve at [0,tu], we use (3) to show that we can use the splitting parameter tutl to split the subcurve to the right, such that the resulting curve will be the subcurve for [tl,tu] of the original curve
Bernstein polynomial symmetry
The symmetry property of Bernstein polynomial is as follows:
Bin(t)=Bn−in(1−t)
This is usually proven, but since it is used here a few times, we will show it as well, as it can be shown pretty quickly.
Bn−in(1−t)=(n−in)(1−t)n−i(1−(1−t))n−(n−i)=(n−in)(1−t)n−iti
With the symmetry of the binomial coefficient (in)=(n−in), we immediately arrive at the result.
(n−in)(1−t)n−iti=(in)(1−t)n−iti=(in)ti(1−t)n−i=Bin(t)
Reversing a Bézier curve
We can reverse a Bézier curve, by reversing the order of the control points.
The control points qi of a reversed curve can be written as:
qi=pn−i
For the proof, we use a new curve parameter s=1−t,s∈[0,1].
So for s=0 we get t=1 and for s=1 we get t=0. So this new parameter traverses the curve from back to front.
We start by replacing the index by the substitution j=n−i⇒i=n−j, so we just reverse the order, which leaves the range unchanged.
p(t)=i=0∑npiBin(t)=j=0∑npn−jBn−jn(t)
We use the Bernstein symmetry.
j=0∑npn−jBn−jn(t)=j=0∑npn−jBjn(1−t)=j=0∑npn−jBjn(s)=j=0∑nqjBjn(s)
By renaming j back to i we get the initially stated property.
The scaling property for Bernstein polynomials
The scaling property of a Bernstein polynomial can be expressed the following way:
Bin(ct)=k=i∑nBik(c)Bkn(t)
Additionally, since Bik=0, if k<i, the loop range can be extended to 0,…,n, as all the additional terms will just evaluate to zero.
ParseError: KaTeX parse error: No such environment: end at position 111: …}_k^n(t)
\begin{̲e̲n̲d̲}̲
We will now derive this expression in detail.
First of, let us start from the left hand side and expand it.
Bin(ct)=(in)(ct)i(1−ct)n−i=(in)citi(1−ct)n−i
A neat trick, that I saw mentioned in the paper The Bernstein polynomial basis: A centennial retrospective by Rida T. Farouki: Replace 1−ct with the equal expression 1−t+t−ct=(1−t)+(1−c)t
Given the next step, this is a quite nice way to split up the ct term in the brackets and have the new terms resemble the 1−x form.
We will use the well known binomial formula to write out the (1−ct)n−1 term.
(x+y)n=k=0∑nxn−kyk
With that we can proceed and multiply out and sort all the resulting factors.
Bin(ct)=(in)citi(1−ct)n−i=(in)citi((1−t)+(1−c)t)n−i=(in)citik=0∑n−i(kn−i)(1−t)n−i−k((1−c)t)k=(in)citik=0∑n−i(kn−i)(1−t)n−i−k(1−c)ktk=k=0∑n−i(in)(kn−i)citi(1−t)n−i−k(1−c)ktk=k=0∑n−i(in)(kn−i)ci(1−c)ktitk(1−t)n−i−k=k=0∑n−i(in)(kn−i)ci(1−c)kti+k(1−t)n−i−k=k=0∑n−i(in)(kn−i)ci(1−c)kti+k(1−t)n−(i+k)
In the last line, we already have a form very similar to a Bernstein polynomial!
First, we will introduce a summation variable change:
u=i+k
From this, we have k=u−i. We also need to change the summation limits. The lower limit first:
ku−iu=0=0=i
So the lower limit is i. As for the upper limit:
ku−iu=n−i=n−i=n
So the upper limit is n. Putting this all in the previous result gives:
k=0∑n−i(in)(kn−i)ci(1−c)kti+k(1−t)n−(i+k)=u=i∑n(in)(u−in−i)ci(1−c)u−iti+u−i(1−t)n−u=u=i∑n(in)(u−in−i)ci(1−c)u−itu(1−t)n−u
These pairs of factors already look like Bernstein polynomials, but the binomial coefficients seem a bit off. So next we will use a binomial coefficient identity, which you can for example find here: Wikipedia.
(hn)(kn−h)=(h+kn)(hh+k)
Setting h=i and k=u−i in this, we get:
(in)(u−in−i)(in)(u−in−i)=(i+u−in)(ii+u−i)=(un)(iu)
Putting this back in:
u=i∑n(in)(u−in−i)ci(1−c)u−itu(1−t)n−u=u=i∑n(un)(iu)ci(1−c)u−itu(1−t)n−u=u=i∑n(iu)ci(1−c)u−i(un)tu(1−t)n−u=u=i∑nBiu(c)Bun(t)=k=i∑nBik(c)Bkn(t)
In the last step, we just renamed the summation variable from u to k. With this,we have arrived, at the final expression of the scaling of the dependent variable! This can now be expressed by a sum of Bernstein polynomials in the scaling and t parameter respectively.
Expression for de Casteljau points
The de Casteljau algorithm produces new points after each step. Each step has one less point compared to the last. In general, the i-th point in the r-th step of the de Casteljau algorithm pir is given by the recurrence relation:
pi0pir(t)i=pi=(1−t)pir−1(t)+tpi+1r−1(t)=1,…,n−r
We can also give an explicit formula for this:
pir(t)=j=0∑rpi+jBjr(t)
We can prove this by induction over r.
First the base case for r=0.
pi0=j=0∑0pi+jBj0(t)=piB00(t)=pi(00)t0(1−t)0=pi
For the induction step, we consider the formula to hold for all r and verify the formula for r+1.
pir+1(t)=(1−t)pir(t)+tpi+1r(t)=(1−t)j=0∑rpi+jBjr(t)+tj=0∑rpi+1+jBjr(t)=j=0∑r(1−t)pi+jBjr(t)+j=0∑rtpi+1+jBjr(t)=j=0∑r(1−t)pi+jBjr(t)+j=1∑r+1tpi+jBj−1r(t)
In the last step, we adjusted the index of the second sum, so the pi+j indices of both sums match. You can easily verify that the actual summation terms are unchanged by checking the first and last indices of the sum (this is just an index subsitution k=j+1 and renaming back to j again at afterwards).
Both sums have different index ranges. The first one ends one too early and the second one starts one too late. So we will just add these indices and then subtract the thusly introduced term. We could also split off the terms that are not shared by both, but that is a bit more wordy, so we choose the first version.
j=0∑r(1−t)pi+jBjr(t)+j=1∑r+1tpi+jBj−1r(t)=j=0∑r+1(1−t)pi+jBjr(t)−(1−t)pi+r+1Br+1r(t)+j=0∑r+1tpi+jBj−1r(t)−tpiB−1r(t)
Now there is a nice thing, in that by definition Bin is 0, if i<0 or i>n (this is basically the shorthand for considering these summation terms manually). So we the terms we added are 0 and thus we subtract 0.
j=0∑r+1(1−t)pi+jBjr(t)−(1−t)pi+r+1Br+1r(t)+j=0∑r+1tpi+jBj−1r(t)−tpiB−1r(t)=j=0∑r+1(1−t)pi+jBjr(t)+j=0∑r+1tpi+jBj−1r(t)=j=0∑r+1((1−t)pi+jBjr(t)+tpi+jBj−1r(t))=j=0∑r+1pi+j((1−t)Bjr(t)+tBj−1r(t))
The Bernstein polynomials too have their recurrence relation Bin(t)=(1−t)Bin−1(t)+tBi−1n−1(t). That way, the last line nicely reduces to the final result, proving the equivalence.
j=0∑r+1pi+j((1−t)Bjr(t)+tBj−1r(t))=j=0∑r+1pi+jBjr+1(t)
Splitting a curve at a parameter to the left
For a given Bézier curve with control points pi we can generate a new curve with the same degree that covers the parameter interval [0,tu] of the original curve, with tu∈[0,1].
The control points of the new curve are given by the first points of the de Casteljau algorithm in each step:
qi=p0i(tu)
The curve is then computed as:
q(s)=i=0∑nqiBin(s)s∈[0,1]
We will now show, that this gives us the original curve in [0,tu].
As s should start (s=0) at the original curve at t=0 and end (s=1) at t=tu, they are related by a simple scaling:
s=tut
We will plug that in and see what comes out
q(s)=i=0∑nqiBin(s)=i=0∑nqiBin(tut)=i=0∑np0i(tu)Bin(tut)=i=0∑nj=0∑ipjBji(tu)Bin(tut)
From this, you can already see the structure of the scaling property of the Bernstein polynomials, but it isn't in the right shape yet.
First, we will use a trick, to make the sums independent of each other, thus allowing the order to be switched around. This makes once again use of the property, that Bin is 0, if i>n. Thus we can expand the inner sum to go to n instead of i, since Bji(tu) will be 0.
i=0∑nj=0∑ipjBji(tu)Bin(tut)=i=0∑nj=0∑npjBji(tu)Bin(tut)=j=0∑ni=0∑npjBji(tu)Bin(tut)=j=0∑npji=0∑nBji(tu)Bin(tut)
The inner sum is now exactly the expression for the scaled Bernstein polynomial:
Bin(ct)=k=0∑nBik(c)Bkn(t)
So we finally get the final result, that is, evaluating the new curve with the s parameter results in the corresponding t point on the original curve.
j=0∑npji=0∑nBji(tu)Bin(tut)=j=0∑npjBjn(t)
Splitting a curve at a parameter to the right
For a given Bézier curve with control points pi we can generate a new curve with the same degree that covers the parameter interval [tl,1] of the original curve, with tl∈[0,1].
The control points of the new curve are given by the last points of the de Casteljau algorithm in each step in reverse:
qi=pin−i(tl)
The curve is then computed as:
q(s)=i=0∑nqiBin(s)s∈[0,1]
We will now show, that this gives us the original curve in [tl,1].
We will show this with our already found properties. First, we will reduce this problem to splitting the curve to the left. For that, we start by reversing the curve, so we start from the end. From before, we know that this curve can be described by reversing the control points. So our reversed curve is:
r(t)ri=i∑nriBin(t)=pn−i
Since we reversed the curve, the split point tl is now located at 1−tl. So we can use our previous properties to get the subcurve from 0 to 1−tl of the reversed curve, which is the original curve from 1 to tl by construction. The split curve is designated by r1−tl.
r1−tl(t)=i=0∑nr0i(1−tl)Bin(t)=i=0∑nj=0∑irjBji(1−tl)Bin(t)
We use the symmetry of B
i=0∑nj=0∑irjBji(1−tl)Bin(t)=i=0∑nj=0∑irjBi−ji(tl)Bin(t)
Insert the definition of rj.
i=0∑nj=0∑irjBi−ji(tl)Bin(t)=i=0∑nj=0∑ipn−jBi−ji(tl)Bin(t)
We start by replacing the outer index i with k to reverse the order, so i=n−k⇒k=n−i. This leaves the loop range the same.
k=0∑nj=0∑n−kpn−jBn−k−jn−k(tl)Bn−kn(t)
Next, we will replace j in the inner loop with r in a way to get to the same index form as from the de Casteljau point definition.
For that we use
n−jjr=k+r=n−k−r=n−j−k
For that substitution, we can find the r values for the loop ranges j=0 and j=n−k, by just plugging in the expression for j.
k=0∑nj=0∑n−kpn−jBn−k−jn−k(tl)Bn−kn(t)=k=0∑nr=0∑n−kpk+rBn−k−(n−k−r)n−k(tl)Bn−kn(t)=k=0∑nr=0∑n−kpk+rBrn−k(tl)Bn−kn(t)
The inner part is now just the definition of a de Casteljau point.
k=0∑nr=0∑n−kpk+rBrn−k(tl)Bn−kn(t)=k=0∑npkn−k(tl)Bn−kn(t)
This curve by construction starts at the original endpoint and ends in the split point. The last thing we will do is reverse this curve again, so we start at the split point and end in the end. Reversal is done with the usual parameter substitution t=1−s and using the symmetry of the Bernstein polynomials again.
r1−tl(t)r1−tl(1−s)=k=0∑npkn−k(tl)Bn−kn(t)=k=0∑npkn−k(tl)Bn−kn(1−s)=k=0∑npkn−k(tl)Bkn(s)=k=0∑nqkBkn(s)
With this we have arrived at the expression for the new control points for the curve from tl to 1.
Parameter in subinterval
When splitting a curve in one defined only in the parameter range [tl,tu]⊆[0,1] we first split the curve to the left to get [0,tu]. Next we split this subcurve again to the right. As this second split occurs in the already split curve, we can't just use tl as the split point, since that is defined in the original curve.
The second split is done with the parameter tutl.
We have already shown this in the section about splitting to the left.
The split curve q(s) is given by:
q(s)qi6=p0i(tu)s∈[0,1]=i=0∑nqiBin(s)
We showed, that for s=tut, q(s) evaluates to ∑j=0npjBjn(t)=p(t).
Therefore q(tutl)=p(tl).