1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-18 06:21:26 +02:00

sections 1-10

This commit is contained in:
Pomax
2017-02-15 16:00:02 -08:00
parent badb1ebed3
commit e9dfc48b89
25 changed files with 1762 additions and 1056 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,54 @@
# de Casteljau's algorithm
If we want to draw Bézier curves we can run through all values of `t` from 0 to 1 and then compute the weighted basis function, getting the `x/y` values we need to plot, but the more complex the curve gets, the more expensive this becomes. Instead, we can use "de Casteljau's algorithm" to draw curves, which is a geometric approach to drawing curves, and really easy to implement. So easy, in fact, you can do it by hand with a pencil and ruler.
Rather than using our calculus function to find `x/y` values for `t`, let's do this instead:
- treat `t` as a ratio (which it is). t=0 is 0% along a line, t=1 is 100% along a line.
- Take all lines between the curve's defining points. For an order `n` curve, that's `n` lines.
- Place markers along each of these line, at distance `t`. So if `t` is 0.2, place the mark at 20% from the start, 80% from the end.
- Now form lines between `those` points. This gives `n-1` lines.
- Place markers along each of these line at distance `t`.
- Form lines between `those` points. This'll be `n-2` lines.
- place markers, form lines, place markers, etc.
- repeat this until you have only one line left. The point `t` on that line coincides with the original curve point at `t`.
<div className="howtocode">
### How to implement de Casteljau's algorithm
Let's just use the algorithm we just specified, and implement that:
```
function drawCurve(points[], t):
if(points.length==1):
draw(points[0])
else:
newpoints=array(points.size-1)
for(i=0; i<newpoints.length; i++):
newpoints[i] = (1-t) * points[i] + t * points[i+1]
drawCurve(newpoints, t)
```
And done, that's the algorithm implemented. Except usually you don't get the luxury of overloading the "+" operator, so let's also give the code for when you need to work with `x` and `y` values:
```
function drawCurve(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)
drawCurve(newpoints, t)
```
So what does this do? This draws a point, if the passed list of points is only 1 point long. Otherwise it will create a new list of points that sit at the <i>t</i> ratios (i.e. the "markers" outlined in the above algorithm), and then call the draw function for this new list.
</div>
To see this in action, mouse-over the following sketch. Moving the mouse changes which curve point is explicitly evaluated using de Casteljau's algorithm, moving the cursor left-to-right (or, of course, right-to-left), shows you how a curve is generated using this approach.
<Graphic preset="simple"title="Traversing a curve using de Casteljau's algorithm" setup={this.setup} draw={this.draw}/>

View File

@@ -1,11 +1,13 @@
var React = require("react"); var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx"); var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var page = "decasteljau";
var deCasteljau = React.createClass({ var deCasteljau = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
title: "de Casteljau's algorithm" title: locale.getTitle(page)
}; };
}, },
@@ -46,72 +48,7 @@ var deCasteljau = React.createClass({
render: function() { render: function() {
return ( return (
<section> <section>{ locale.getContent(page, this) }</section>
<SectionHeader {...this.props} />
<p>If we want to draw Bézier curves we can run through all values of <i>t</i> from 0 to 1 and then
compute the weighted basis function, getting the <i>x</i>/<i>y</i> values we need to plot, but the
more complex the curve gets, the more expensive this becomes. Instead, we can use "de Casteljau's
algorithm" to draw curves, which is a geometric approach to drawing curves, and really easy to
implement. So easy, in fact, you can do it by hand with a pencil and ruler.</p>
<p>Rather than using our calculus function to find <i>x</i>/<i>y</i> values for <i>t</i>, let's
do this instead:</p>
<ul>
<li>treat <i>t</i> as a ratio (which it is). t=0 is 0% along a line, t=1 is 100% along a line.</li>
<li>Take all lines between the curve's defining points. For an order <i>n</i> curve, that's <i>n</i> lines.</li>
<li>Place markers along each of these line, at distance <i>t</i>. So if <i>t</i> is 0.2, place the mark
at 20% from the start, 80% from the end.</li>
<li>Now form lines between <i>those</i> points. This gives <i>n-1</i> lines.</li>
<li>Place markers along each of these line at distance <i>t</i>.</li>
<li>Form lines between <i>those</i> points. This'll be <i>n-2</i> lines.</li>
<li>place markers, form lines, place markers, etc.</li>
<li>repeat this until you have only one line left. The point <i>t</i> on that line coincides with the
original curve point at <i>t</i>.</li>
</ul>
<div className="howtocode">
<h3>How to implement de Casteljau's algorithm</h3>
<p>Let's just use the algorithm we just specified, and implement that:</p>
<pre>function drawCurve(points[], t):
if(points.length==1):
draw(points[0])
else:
newpoints=array(points.size-1)
for(i=0; i<newpoints.length; i++):
newpoints[i] = (1-t) * points[i] + t * points[i+1]
drawCurve(newpoints, t)</pre>
<p>And done, that's the algorithm implemented. Except usually you don't get the luxury of
overloading the "+" operator, so let's also give the code for when you need to work with
<i>x</i> and <i>y</i> values:</p>
<pre>function drawCurve(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)
drawCurve(newpoints, t)</pre>
<p>So what does this do? This draws a point, if the passed list of points is only 1 point
long. Otherwise it will create a new list of points that sit at the <i>t</i> ratios (i.e.
the "markers" outlined in the above algorithm), and then call the draw function for this
new list.</p>
</div>
<p>To see this in action, mouse-over the following sketch. Moving the mouse changes which curve point is
explicitly evaluated using de Casteljau's algorithm, moving the cursor left-to-right (or, of course,
right-to-left), shows you how a curve is generated using this approach.</p>
<Graphic preset="simple"title="Traversing a curve using de Casteljau's algorithm" setup={this.setup} draw={this.draw}/>
</section>
); );
} }
}); });

View File

@@ -0,0 +1,42 @@
# Simplified drawing
We can also simplify the drawing process by "sampling" the curve at certain points, and then joining those points up with straight lines, a process known as "flattening", as we are reducing a curve to a simple sequence of straight, "flat" lines.
We can do this is by saying "we want X segments", and then sampling the curve at intervals that are spaced such that we end up with the number of segments we wanted. The advantage of this method is that it's fast: instead of evaluating 100 or even 1000 curve coordinates, we can sample a much lower number and still end up with a curve that sort-of-kind-of looks good enough. The disadvantage of course is that we lose the precision of working with "the real curve", so we usually can't use the flattened for for doing true intersection detection, or curvature alignment.
<Graphic preset="twopanel" title="Flattening a quadratic curve" setup={this.setupQuadratic} draw={this.drawFlattened} onKeyDown={this.onKeyDown}/>
<Graphic preset="twopanel" title="Flattening a cubic curve" setup={this.setupCubic} draw={this.drawFlattened} onKeyDown={this.onKeyDown} />
Try clicking on the sketch and using your up and down arrow keys to lower the number of segments for both the quadratic and cubic curve. You'll notice that for certain curvatures, a low number of segments works quite well, but for more complex curvatures (try this for the cubic curve), a higher number is required to capture the curvature changes properly.
<div className="howtocode">
### How to implement curve flattening
Let's just use the algorithm we just specified, and implement that:
```
function flattenCurve(curve, segmentCount):
step = 1/segmentCount;
coordinates = [curve.getXValue(0), curve.getYValue(0)]
for(i=1; i <= segmentCount; i++):
t = i*step;
coordinates.push[curve.getXValue(t), curve.getYValue(t)]
return coordinates;
```
And done, that's the algorithm implemented. That just leaves drawing the resulting "curve" as a sequence of lines:
```
function drawFlattenedCurve(curve, segmentCount):
coordinates = flattenCurve(curve, segmentCount)
coord = coordinates[0], _coords;
for(i=1; i < coordinates.length; i++):
_coords = coordinates[i]
line(coords, _coords)
coords = _coords
```
We start with the first coordinate as reference point, and then just draw lines between each point and its next point.
</div>

View File

@@ -1,6 +1,9 @@
var React = require("react"); var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx"); var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var page = "flattening";
var keyHandling = require("../../decorators/keyhandling-decorator.jsx"); var keyHandling = require("../../decorators/keyhandling-decorator.jsx");
var Flattening = React.createClass({ var Flattening = React.createClass({
@@ -21,7 +24,7 @@ var Flattening = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
title: "Simplified drawing" title: locale.getTitle(page)
}; };
}, },
@@ -73,51 +76,7 @@ var Flattening = React.createClass({
render: function() { render: function() {
return ( return (
<section> <section>{ locale.getContent(page, this) }</section>
<SectionHeader {...this.props} />
<p>We can also simplify the drawing process by "sampling" the curve at certain points, and then joining those points up with straight lines, a process known as "flattening", as we are reducing a curve to a simple sequence of straight, "flat" lines.</p>
<p>We can do this is by saying "we want X segments", and then sampling the curve at intervals that are spaced such that we
end up with the number of segments we wanted. The advantage of this method is that it's fast: instead of evaluating 100 or
even 1000 curve coordinates, we can sample a much lower number and still end up with a curve that sort-of-kind-of looks good
enough. The disadvantage of course is that we lose the precision of working with "the real curve", so we usually can't use
the flattened for for doing true intersection detection, or curvature alignment.</p>
<Graphic preset="twopanel" title="Flattening a quadratic curve" setup={this.setupQuadratic} draw={this.drawFlattened} onKeyDown={this.onKeyDown}/>
<Graphic preset="twopanel" title="Flattening a cubic curve" setup={this.setupCubic} draw={this.drawFlattened} onKeyDown={this.onKeyDown} />
<p>Try clicking on the sketch and using your up and down arrow keys to lower the number of segments for both the quadratic
and cubic curve. You'll notice that for certain curvatures, a low number of segments works quite well, but for more complex
curvatures (try this for the cubic curve), a higher number is required to capture the curvature changes properly.</p>
<div className="howtocode">
<h3>How to implement curve flattening</h3>
<p>Let's just use the algorithm we just specified, and implement that:</p>
<pre>function flattenCurve(curve, segmentCount):
step = 1/segmentCount;
coordinates = [curve.getXValue(0), curve.getYValue(0)]
for(i=1; i <= segmentCount; i++):
t = i*step;
coordinates.push[curve.getXValue(t), curve.getYValue(t)]
return coordinates;</pre>
<p>And done, that's the algorithm implemented. That just leaves drawing the resulting "curve" as a sequence of lines:</p>
<pre>function drawFlattenedCurve(curve, segmentCount):
coordinates = flattenCurve(curve, segmentCount)
coord = coordinates[0], _coords;
for(i=1; i < coordinates.length; i++):
_coords = coordinates[i]
line(coords, _coords)
coords = _coords</pre>
<p>We start with the first coordinate as reference point, and then just draw lines between each point and its next point.</p>
</div>
</section>
); );
} }
}); });

View File

@@ -0,0 +1,120 @@
# Bézier curvatures as matrix operations
We can also represent Bézier as matrix operations, by expressing the Bézier formula as a polynomial basis function, the weight matrix, and the actual coordinates as matrix. Let's look at what this means for the cubic curve:
\[
B(t) = P_1 \cdot (1-t)^3 + P_2 \cdot 3 \cdot (1-t)^2 \cdot t + P_3 \cdot 3 \cdot (1-t) \cdot t^2 + P_4 \cdot t^3
\]
Disregarding our actual coordinates for a moment, we have:
\[
B(t) = (1-t)^3 + 3 \cdot (1-t)^2 \cdot t + 3 \cdot (1-t) \cdot t^2 + t^3
\]
We can write this as a sum of four expressions:
\[
\begin{matrix}
... & = & (1-t)^3 \\
& + & 3 \cdot (1-t)^2 \cdot t \\
& + & 3 \cdot (1-t) \cdot t^2 \\
& + & t^3 \\
\end{matrix}
\]
And we can expand these expressions:
\[
\begin{matrix}
... & = & (1-t) \cdot (1-t) \cdot (1-t) & = & -t^3 + 3 \cdot t^2 - 3 \cdot t + 1 \\
& + & 3 \cdot (1-t) \cdot (1-t) \cdot t & = & 3 \cdot t^3 - 6 \cdot t^2 + 3 \cdot t \\
& + & 3 \cdot (1-t) \cdot t \cdot t & = & -3 \cdot t^3 + 3 \cdot t^2 \\
& + & t \cdot t \cdot t & = & t^3 \\
\end{matrix}
\]
Furthermore, we can make all the 1 and 0 factors explicit:
\[
\begin{matrix}
... & = & -1 \cdot t^3 + 3 \cdot t^2 - 3 \cdot t + 1 \\
& + & +3 \cdot t^3 - 6 \cdot t^2 + 3 \cdot t + 0 \\
& + & -3 \cdot t^3 + 3 \cdot t^2 + 0 \cdot t + 0 \\
& + & +1 \cdot t^3 + 0 \cdot t^2 + 0 \cdot t + 0 \\
\end{matrix}
\]
And *that*, we can view as a series of four matrix operations:
\[
\begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}-1 \\ 3 \\ -3 \\ 1\end{bmatrix}
+ \begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}3 \\ -6 \\ 3 \\ 0\end{bmatrix}
+ \begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}-3 \\ 3 \\ 0 \\ 0\end{bmatrix}
+ \begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}1 \\ 0 \\ 0 \\ 0\end{bmatrix}
\]
If we compact this into a single matrix operation, we get:
\[
\begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}
-1 & 3 & -3 & 1 \\
3 & -6 & 3 & 0 \\
-3 & 3 & 0 & 0 \\
1 & 0 & 0 & 0
\end{bmatrix}
\]
This kind of polynomial basis representation is generally written with the bases in increasing order, which means we need to flip our `t` matrix horizontally, and our big "mixing" matrix upside down:
\[
\begin{bmatrix}1 & t & t^2 & t^3\end{bmatrix} \cdot \begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\]
And then finally, we can add in our original coordinates as a single third matrix:
\[
B(t) = \begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]
We can perform the same trick for the quadratic curve, in which case we end up with:
\[
B(t) = \begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
If we plug in a `t` value, and then multiply the matrices, we will get exactly the same values as when we evaluate the original polynomial function, or as when we evaluate the curve using progessive linear interpolation.
**So: why would we bother with matrices?** Matrix representations allow us to discover things about functions that would otherwise be hard to tell. It turns out that the curves form [triangular matrices](https://en.wikipedia.org/wiki/Triangular_matrix), and they have a determinant equal to the product of the actual coordinates we use for our curve. It's also invertible, which means there's [a ton of properties](https://en.wikipedia.org/wiki/Invertible_matrix#The_invertible_matrix_theorem) that are all satisfied. Of course, the main question is: "Why is this useful to us, now?", and the answer to that is that it's not immediately useful, but you'll be seeing some instances where certain curve properties can be either computed via function manipulation, or via clever use of matrices, and sometimes the matrix approach can be (drastically) faster.
So for now, just remember that we can represent curves this way, and let's move on.

View File

@@ -1,153 +1,19 @@
var React = require("react"); var React = require("react");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var page = "matrix";
var Matrix = React.createClass({ var Matrix = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
title: "Bézier curvatures as matrix operations" title: locale.getTitle(page)
}; };
}, },
render: function() { render: function() {
return ( return (
<section> <section>{ locale.getContent(page, this) }</section>
<SectionHeader {...this.props} />
<p>We can also represent Bézier as matrix operations, by expressing the Bézier formula
as a polynomial basis function, the weight matrix, and the actual coordinates as matrix.
Let's look at what this means for the cubic curve :</p>
<p>\[
B(t) = P_1 \cdot (1-t)^3 + P_2 \cdot 3 \cdot (1-t)^2 \cdot t + P_3 \cdot 3 \cdot (1-t) \cdot t^2 + P_4 \cdot t^3
\]</p>
<p>Disregarding our actual coordinates for a moment, we have:</p>
<p>\[
B(t) = (1-t)^3 + 3 \cdot (1-t)^2 \cdot t + 3 \cdot (1-t) \cdot t^2 + t^3
\]</p>
<p>We can write this as a sum of four expressions:</p>
<p>\[
\begin{matrix}
... & = & (1-t)^3 \\
& + & 3 \cdot (1-t)^2 \cdot t \\
& + & 3 \cdot (1-t) \cdot t^2 \\
& + & t^3 \\
\end{matrix}
\]</p>
<p>And we can expand these expressions:</p>
<p>\[
\begin{matrix}
... & = & (1-t) \cdot (1-t) \cdot (1-t) & = & -t^3 + 3 \cdot t^2 - 3 \cdot t + 1 \\
& + & 3 \cdot (1-t) \cdot (1-t) \cdot t & = & 3 \cdot t^3 - 6 \cdot t^2 + 3 \cdot t \\
& + & 3 \cdot (1-t) \cdot t \cdot t & = & -3 \cdot t^3 + 3 \cdot t^2 \\
& + & t \cdot t \cdot t & = & t^3 \\
\end{matrix}
\]</p>
<p>Furthermore, we can make all the 1 and 0 factors explicit:</p>
<p>\[
\begin{matrix}
... & = & -1 \cdot t^3 + 3 \cdot t^2 - 3 \cdot t + 1 \\
& + & +3 \cdot t^3 - 6 \cdot t^2 + 3 \cdot t + 0 \\
& + & -3 \cdot t^3 + 3 \cdot t^2 + 0 \cdot t + 0 \\
& + & +1 \cdot t^3 + 0 \cdot t^2 + 0 \cdot t + 0 \\
\end{matrix}
\]</p>
<p>And <em>that</em>, we can view as a series of four matrix operations:</p>
<p>\[
\begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}-1 \\ 3 \\ -3 \\ 1\end{bmatrix}
+ \begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}3 \\ -6 \\ 3 \\ 0\end{bmatrix}
+ \begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}-3 \\ 3 \\ 0 \\ 0\end{bmatrix}
+ \begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}1 \\ 0 \\ 0 \\ 0\end{bmatrix}
\]</p>
<p>If we compact this into a single matrix operation, we get:</p>
<p>\[
\begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}
-1 & 3 & -3 & 1 \\
3 & -6 & 3 & 0 \\
-3 & 3 & 0 & 0 \\
1 & 0 & 0 & 0
\end{bmatrix}
\]</p>
<p>This kind of polynomial basis representation is generally written with the bases in
increasing order, which means we need to flip our <em>t</em> matrix horizontally, and our
big "mixing" matrix upside down:</p>
<p>\[
\begin{bmatrix}1 & t & t^2 & t^3\end{bmatrix} \cdot \begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\]</p>
<p>And then finally, we can add in our original coordinates as a single third matrix:</p>
<p>\[
B(t) = \begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]</p>
<p>We can perform the same trick for the quadratic curve, in which case we end up with:</p>
<p>\[
B(t) = \begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]</p>
<p>If we plug in a <em>t</em> value, and then multiply the matrices, we will
get exactly the same values as when we evaluate the original polynomial function,
or as when we evaluate the curve using progessive linear interpolation.</p>
<p><strong>So: why would we bother with matrices?</strong> Matrix representations
allow us to discover things about functions that would otherwise be hard to tell.
It turns out that the curves form <a href="https://en.wikipedia.org/wiki/Triangular_matrix">triangular
matrices</a>, and they have a determinant equal to the product of the actual
coordinates we use for our curve. It's also invertible, which means there's
<a href="https://en.wikipedia.org/wiki/Invertible_matrix#The_invertible_matrix_theorem">a
ton of properties</a> that are all satisfied. Of course, the main question is:
"Why is this useful to us, now?", and the answer to that is that it's not
immediately useful, but you'll be seeing some instances where certain curve
properties can be either computed via function manipulation, or via clever
use of matrices, and sometimes the matrix approach can be (drastically) faster.</p>
<p>So for now, just remember that we can represent curves this way, and let's move on.</p>
</section>
); );
} }
}); });

View File

@@ -0,0 +1,567 @@
# Splitting curves using matrices
Another way to split curves is to exploit the matrix representation of a Bézier curve. In <a href="#matrix">the section on matrices</a> we saw that we can represent curves as matrix multiplications. Specifically, we saw these two forms for the quadratic, and cubic curves, respectively (using the reversed Bézier coefficients vector for legibility):
\[
B(t) = \begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
and
\[
B(t) = \begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0\\
-3 & 3 & 0 & 0\\
3 & -6 & 3 & 0\\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]
Let's say we want to split the curve at some point `t = z`, forming two new (obviously smaller) Bézier curves. To find the coordinates for these two Bézier curves, we can use the matrix representation and some linear algebra. First, we split out the the actual "point on the curve" information as a new matrix multiplication:
\[
B(t) =
\begin{bmatrix}
1 & (z \cdot t) & (z \cdot t)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
0 & z & 0 \\
0 & 0 & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
and
\[
B(t) =
\begin{bmatrix}
1 & (z \cdot t) & (z \cdot t)^2 & (z \cdot t)^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0\\
0 & z & 0 & 0\\
0 & 0 & z^2 & 0\\
0 & 0 & 0 & z^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]
If we could compact these matrices back to a form **[t values] · [bezier matrix] · [column matrix]**, with the first two staying the same, then that column matrix on the right would be the coordinates of a new Bézier curve that describes the first segment, from `t = 0` to `t = z`. As it turns out, we can do this quite easily, by exploiting some simple rules of linear algebra (and if you don't care about the derivations, just skip to the end of the box for the results!).
<div className="note">
## Deriving new hull coordinates
Deriving the two segments upon splitting a curve takes a few steps, and the higher the curve order, the more work it is, so let's look at the quadratic curve first:
\[
B(t) =
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
0 & z & 0 \\
0 & 0 & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\underset{we\ turn\ this...}{\underbrace{\kern 2.25em Z \cdot M \kern 2.25em}}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\underset{...into\ this...}{\underbrace{ M \cdot M^{-1} \cdot Z \cdot M }}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
M
\underset{...to\ get\ \ this!}{\underbrace{ \kern 1.25em \cdot \kern 1.25em Q \kern 1.25em \cdot \kern 1.25em}}
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
We do this, because [*M · M<sup>-1</sup>*] is the identity matrix (a bit like multiplying something by x/x in calculus. It doesn't do anything to the function, but it does allow you to rewrite it to something that may be easier to work with, or can be broken up differently). Adding that as matrix multiplication has no effect on the total formula, but it does allow us to change the matrix sequence [*something · M*] to a sequence [*M · something*], and that makes a world of difference: if we know what [*M<sup>-1</sup> · Z · M*] is, we can apply that to our coordinates, and be left with a proper matrix representation of a quadratic Bézier curve (which is [*T · M · P*]), with a new set of coordinates that represent the curve from *t = 0* to *t = z*. So let's get computing:
\[
Q = M^{-1} \cdot Z \cdot M =
\begin{bmatrix}
1 & 0 & 0 \\
1 & \frac{1}{2} & 0 \\
1 & 1 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
0 & z & 0 \\
0 & 0 & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
=
\begin{bmatrix}
1 & 0 & 0 \\
-(z-1) & z & 0 \\
(z - 1)^2 & -2 \cdot (z-1) \cdot z & z^2
\end{bmatrix}
\]
Excellent! Now we can form our new quadratic curve:
\[
B(t) =
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot M \cdot Q \cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
M
\cdot
\left (
Q
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\right )
\]
\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\left (
\begin{bmatrix}
1 & 0 & 0 \\
-(z-1) & z & 0 \\
(z - 1)^2 & -2 \cdot (z-1) \cdot z & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\right )
\]
\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\
z \cdot P_2 - (z-1) \cdot P_1 \\
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z - 1)^2 \cdot P_1
\end{bmatrix}
\]
***Brilliant***: if we want a subcurve from `t = 0` to `t = z`, we can keep the first coordinate the same (which makes sense), our control point becomes a z-ratio mixture of the original control point and the start point, and the new end point is a mixture that looks oddly similar to a bernstein polynomial of degree two, except it uses (z-1) rather than (1-z)... These new coordinates are actually really easy to compute directly!
Of course, that's only one of the two curves. Getting the section from `t = z` to `t = 1` requires doing this again. We first observe what what we just did is actually evaluate the general interval [0,`z`], which we wrote down simplified becuase of that zero, but we actually evaluated this:
\[
B(t) =
\begin{bmatrix}
1 & ( 0 + z \cdot t) & ( 0 + z \cdot t)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
0 & z & 0 \\
0 & 0 & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
If we want the interval [*z*,1], we will be evaluating this instead:
\[
B(t) =
\begin{bmatrix}
1 & ( z + (1-z) \cdot t) & ( z + (1-z) \cdot t)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & z & z^2 \\
0 & 1-z & 2 \cdot z \cdot (1-z) \\
0 & 0 & (1-z)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
We're going to do the same trick, to turn `[something · M]` into `[M · something]`:
\[
Q' = M^{-1} \cdot Z' \cdot M =
\begin{bmatrix}
1 & 0 & 0 \\
1 & \frac{1}{2} & 0 \\
1 & 1 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & z & z^2 \\
0 & 1-z & 2 \cdot z \cdot (1-z) \\
0 & 0 & (1-z)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
=
\begin{bmatrix}
(z-1)^2 & -2 \cdot z \cdot (z-1) & z^2 \\
0 & -(z-1) & z \\
0 & 0 & 1
\end{bmatrix}
\]
So, our final second curve looks like:
\[
B(t) =
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot M \cdot Q \cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
M
\cdot
\left (
Q'
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\right )
\]
\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\left (
\begin{bmatrix}
(z-1)^2 & -2 \cdot z \cdot (z-1) & z^2 \\
0 & -(z-1) & z \\
0 & 0 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\right )
\]
\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z-1)^2 \cdot P_1 \\
z \cdot P_3 - (z-1) \cdot P_2 \\
P_3
\end{bmatrix}
\]
***Nice***: we see the same as before; can keep the last coordinate the same (which makes sense), our control point becomes a z-ratio mixture of the original control point and the end point, and the new start point is a mixture that looks oddly similar to a bernstein polynomial of degree two, except it uses (z-1) rather than (1-z). These new coordinates are *also* really easy to compute directly!
</div>
So, using linear algebra rather than de Casteljau's algorithm, we have determined that for any quadratic curve split at some value `t = z`, we get two subcurves that are described as Bézier curves with simple-to-derive coordinates.
\[
\begin{bmatrix}
1 & 0 & 0 \\
-(z-1) & z & 0 \\
(z - 1)^2 & -2 \cdot (z-1) \cdot z & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
P_1 \\
z \cdot P_2 - (z-1) \cdot P_1 \\
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z - 1)^2 \cdot P_1
\end{bmatrix}
\]
and
\[
\begin{bmatrix}
(z-1)^2 & -2 \cdot z \cdot (z-1) & z^2 \\
0 & -(z-1) & z \\
0 & 0 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z-1)^2 \cdot P_1 \\
z \cdot P_3 - (z-1) \cdot P_2 \\
P_3
\end{bmatrix}
\]
We can do the same for cubic curves. However, I'll spare you the actual derivation (don't let that stop you from writing that out yourself, though) and simply show you the resulting new coordinate sets:
\[
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-(z-1) & z & 0 & 0 \\
(z-1)^2 & -2 \cdot (z-1) \cdot z & z^2 & 0 \\
-(z-1)^3 & 3 \cdot (z-1)^2 \cdot z & -3 \cdot (z-1) \cdot z^2 & z^3
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
P_1 \\
z \cdot P_2 - (z-1) \cdot P_1 \\
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z-1)^2 \cdot P_1 \\
z^3 \cdot P_4 - 3 \cdot z^2 \cdot (z-1) \cdot P_3 + 3 \cdot z \cdot (z-1)^2 \cdot P_2 - (z-1)^3 \cdot P_1
\end{bmatrix}
\]
and
\[
\begin{bmatrix}
-(z-1)^3 & 3 \cdot (z-1)^2 \cdot z & -3 \cdot (z-1)^3 \cdot z^2 & z^3 \\
0 & (z-1)^2 & -2 \cdot (z-1) \cdot z & z^2 \\
0 & 0 & -(z-1) & z \\
0 & 0 & 0 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
z^3 \cdot P_4 - 3 \cdot z^2 \cdot (z-1) \cdot P_3 + 3 \cdot z \cdot (z-1)^2 \cdot P_2 - (z-1)^3 \cdot P_1 \\
z^2 \cdot P_4 - 2 \cdot z \cdot (z-1) \cdot P_3 + (z-1)^2 \cdot P_2 \\
z \cdot P_4 - (z-1) \cdot P_3 \\
P_4
\end{bmatrix}
\]
So, looking at our matrices, did we really need to compute the second segment matrix? No, we didn't. Actually having one segment's matrix means we implicitly have the other: push the values of each row in the matrix ***Q*** to the right, with zeroes getting pushed off the right edge and appearing back on the left, and then flip the matrix vertically. Presto, you just "calculated" ***Q'***.
Implementing curve splitting this way requires less recursion, and is just straight arithmetic with cached values, so can be cheaper on systems were recursion is expensive. If you're doing computation with devices that are good at matrix multiplication, chopping up a Bézier curve with this method will be a lot faster than applying de Casteljau.

View File

@@ -1,628 +1,19 @@
var React = require("react"); var React = require("react");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var page = "matrixsplit";
var MatrixSplit = React.createClass({ var MatrixSplit = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
title: "Splitting curves using matrices" title: locale.getTitle(page)
}; };
}, },
render: function() { render: function() {
return ( return (
<section> <section>{ locale.getContent(page, this) }</section>
<SectionHeader {...this.props} />
<p>Another way to split curves is to exploit the matrix representation of
a Bézier curve. In <a href="#matrix">the section on matrices</a> we saw that
we can represent curves as matrix multiplications. Specifically, we saw these
two forms for the quadratic, and cubic curves, respectively (using the reversed
Bézier coefficients vector for legibility):</p>
<p>\[
B(t) = \begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]</p>
<p>and</p>
<p>\[
B(t) = \begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0\\
-3 & 3 & 0 & 0\\
3 & -6 & 3 & 0\\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]</p>
<p>Let's say we want to split the curve at some point <em>t = z</em>, forming
two new (obviously smaller) Bézier curves. To find the coordinates for these
two Bézier curves, we can use the matrix representation and some linear algebra.
First, we split out the the actual "point on the curve" information as a new matrix
multiplication:</p>
<p>\[
B(t) =
\begin{bmatrix}
1 & (z \cdot t) & (z \cdot t)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
0 & z & 0 \\
0 & 0 & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]</p>
<p>and</p>
<p>\[
B(t) =
\begin{bmatrix}
1 & (z \cdot t) & (z \cdot t)^2 & (z \cdot t)^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0\\
0 & z & 0 & 0\\
0 & 0 & z^2 & 0\\
0 & 0 & 0 & z^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]</p>
<p>If we could compact these matrices back to a form <strong>[t values] · [bezier matrix] · [column matrix]</strong>,
with the first two staying the same, then that column matrix on the right would be the coordinates
of a new Bézier curve that describes the first segment, from <em>t = 0</em> to <em>t = z</em>.
As it turns out, we can do this quite easily, by exploiting some simple rules of linear algebra
(and if you don't care about the derivations, just skip to the end of the box for the results!).</p>
<div className="note">
<h2>Deriving new hull coordinates</h2>
<p>Deriving the two segments upon splitting a curve takes a few steps, and the higher
the curve order, the more work it is, so let's look at the quadratic curve first:</p>
<p>\[
B(t) =
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
0 & z & 0 \\
0 & 0 & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]</p>
<p>\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\underset{we\ turn\ this...}{\underbrace{\kern 2.25em Z \cdot M \kern 2.25em}}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]</p>
<p>\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\underset{...into\ this...}{\underbrace{ M \cdot M^{-1} \cdot Z \cdot M }}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]</p>
<p>\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
M
\underset{...to\ get\ \ this!}{\underbrace{ \kern 1.25em \cdot \kern 1.25em Q \kern 1.25em \cdot \kern 1.25em}}
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]</p>
<p>We do this, because [<em>M · M<sup>-1</sup></em>] is the identity matrix (a bit like
multiplying something by x/x in calculus. It doesn't do anything to the function, but it
does allow you to rewrite it to something that may be easier to work with, or can be
broken up differently). Adding that as matrix multiplication has no effect on the total
formula, but it does allow us to change the matrix sequence [<em>something · M</em>] to
a sequence [<em>M · something</em>], and that makes a world of difference: if we know
what [<em>M<sup>-1</sup> · Z · M</em>] is, we can apply that to our coordinates, and be
left with a proper matrix representation of a quadratic Bézier curve (which is
[<em>T · M · P</em>]), with a new set of coordinates that represent the curve from
<em>t = 0</em> to <em>t = z</em>. So let's get computing:</p>
<p>\[
Q = M^{-1} \cdot Z \cdot M =
\begin{bmatrix}
1 & 0 & 0 \\
1 & \frac{1}{2} & 0 \\
1 & 1 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
0 & z & 0 \\
0 & 0 & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
=
\begin{bmatrix}
1 & 0 & 0 \\
-(z-1) & z & 0 \\
(z - 1)^2 & -2 \cdot (z-1) \cdot z & z^2
\end{bmatrix}
\]</p>
<p>Excellent! Now we can form our new quadratic curve:</p>
<p>\[
B(t) =
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot M \cdot Q \cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
M
\cdot
\left (
Q
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\right )
\]</p>
<p>\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\left (
\begin{bmatrix}
1 & 0 & 0 \\
-(z-1) & z & 0 \\
(z - 1)^2 & -2 \cdot (z-1) \cdot z & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\right )
\]</p>
<p>\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\
z \cdot P_2 - (z-1) \cdot P_1 \\
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z - 1)^2 \cdot P_1
\end{bmatrix}
\]</p>
<p><strong><em>Brilliant</em></strong>: if we want a subcurve from <em>t = 0</em>
to <em>t = z</em>, we can keep the first coordinate the same (which makes sense),
our control point becomes a z-ratio mixture of the original control point and the start
point, and the new end point is a mixture that looks oddly similar to a bernstein
polynomial of degree two, except it uses (z-1) rather than (1-z)... These new
coordinates are actually really easy to compute directly!</p>
<p>Of course, that's only one of the two curves. Getting the section from <em>t = z</em>
to <em>t = 1</em> requires doing this again. We first observe what what we just did is
actually evaluate the general interval [0,<em>z</em>], which we wrote down simplified
becuase of that zero, but we actually evaluated this:</p>
<p>\[
B(t) =
\begin{bmatrix}
1 & ( 0 + z \cdot t) & ( 0 + z \cdot t)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]</p>
<p>\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
0 & z & 0 \\
0 & 0 & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]</p>
<p>If we want the interval [<em>z</em>,1], we will be evaluating this instead:</p>
<p>\[
B(t) =
\begin{bmatrix}
1 & ( z + (1-z) \cdot t) & ( z + (1-z) \cdot t)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]</p>
<p>\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & z & z^2 \\
0 & 1-z & 2 \cdot z \cdot (1-z) \\
0 & 0 & (1-z)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]</p>
<p>We're going to do the same trick, to turn <em>[something · M]</em> into <em>[M · something]</em>:</p>
<p>\[
Q' = M^{-1} \cdot Z' \cdot M =
\begin{bmatrix}
1 & 0 & 0 \\
1 & \frac{1}{2} & 0 \\
1 & 1 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & z & z^2 \\
0 & 1-z & 2 \cdot z \cdot (1-z) \\
0 & 0 & (1-z)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
=
\begin{bmatrix}
(z-1)^2 & -2 \cdot z \cdot (z-1) & z^2 \\
0 & -(z-1) & z \\
0 & 0 & 1
\end{bmatrix}
\]</p>
<p>So, our final second curve looks like:</p>
<p>\[
B(t) =
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot M \cdot Q \cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
M
\cdot
\left (
Q'
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\right )
\]</p>
<p>\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\left (
\begin{bmatrix}
(z-1)^2 & -2 \cdot z \cdot (z-1) & z^2 \\
0 & -(z-1) & z \\
0 & 0 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\right )
\]</p>
<p>\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z-1)^2 \cdot P_1 \\
z \cdot P_3 - (z-1) \cdot P_2 \\
P_3
\end{bmatrix}
\]</p>
<p><strong><em>Nice</em></strong>: we see the same as before; can keep the last
coordinate the same (which makes sense), our control point becomes a z-ratio
mixture of the original control point and the end point, and the new start point
is a mixture that looks oddly similar to a bernstein polynomial of degree two,
except it uses (z-1) rather than (1-z). These new coordinates are <em>also</em>
really easy to compute directly!</p>
</div>
<p>So, using linear algebra rather than de Casteljau's algorithm, we have determined
that for any quadratic curve split at some value <em>t = z</em>, we get two subcurves
that are described as Bézier curves with simple-to-derive coordinates.</p>
<p>\[
\begin{bmatrix}
1 & 0 & 0 \\
-(z-1) & z & 0 \\
(z - 1)^2 & -2 \cdot (z-1) \cdot z & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
P_1 \\
z \cdot P_2 - (z-1) \cdot P_1 \\
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z - 1)^2 \cdot P_1
\end{bmatrix}
\]</p>
<p>and</p>
<p>\[
\begin{bmatrix}
(z-1)^2 & -2 \cdot z \cdot (z-1) & z^2 \\
0 & -(z-1) & z \\
0 & 0 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z-1)^2 \cdot P_1 \\
z \cdot P_3 - (z-1) \cdot P_2 \\
P_3
\end{bmatrix}
\]</p>
<p>We can do the same for cubic curves. However, I'll spare you the actual derivation
(don't let that stop you from writing that out yourself, though) and simply show you
the resulting new coordinate sets:</p>
<p>\[
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-(z-1) & z & 0 & 0 \\
(z-1)^2 & -2 \cdot (z-1) \cdot z & z^2 & 0 \\
-(z-1)^3 & 3 \cdot (z-1)^2 \cdot z & -3 \cdot (z-1) \cdot z^2 & z^3
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
P_1 \\
z \cdot P_2 - (z-1) \cdot P_1 \\
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z-1)^2 \cdot P_1 \\
z^3 \cdot P_4 - 3 \cdot z^2 \cdot (z-1) \cdot P_3 + 3 \cdot z \cdot (z-1)^2 \cdot P_2 - (z-1)^3 \cdot P_1
\end{bmatrix}
\]</p>
<p>and</p>
<p>\[
\begin{bmatrix}
-(z-1)^3 & 3 \cdot (z-1)^2 \cdot z & -3 \cdot (z-1)^3 \cdot z^2 & z^3 \\
0 & (z-1)^2 & -2 \cdot (z-1) \cdot z & z^2 \\
0 & 0 & -(z-1) & z \\
0 & 0 & 0 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
z^3 \cdot P_4 - 3 \cdot z^2 \cdot (z-1) \cdot P_3 + 3 \cdot z \cdot (z-1)^2 \cdot P_2 - (z-1)^3 \cdot P_1 \\
z^2 \cdot P_4 - 2 \cdot z \cdot (z-1) \cdot P_3 + (z-1)^2 \cdot P_2 \\
z \cdot P_4 - (z-1) \cdot P_3 \\
P_4
\end{bmatrix}
\]</p>
<p>So, looking at our matrices, did we really need to compute the second segment matrix?
No, we didn't. Actually having one segment's matrix means we implicitly have the other:
push the values of each row in the matrix <strong><em>Q</em></strong> to the right, with
zeroes getting pushed off the right edge and appearing back on the left, and then flip
the matrix vertically. Presto, you just "calculated" <strong><em>Q'</em></strong>.</p>
<p>Implementing curve splitting this way requires less recursion, and is just straight
arithmetic with cached values, so can be cheaper on systems were recursion is expensive.
If you're doing computation with devices that are good at matrix multiplication, chopping
up a Bézier curve with this method will be a lot faster than applying de Casteljau.</p>
</section>
); );
} }
}); });

View File

@@ -0,0 +1,38 @@
# Splitting curves
With de Casteljau's algorithm we also find all the points we need to split up a Bézier curve into two, smaller curves, which taken together form the original curve. When we construct de Casteljau's skeleton for some value `t`, the procedure gives us all the points we need to split a curve at that `t` value: one curve is defined by all the inside skeleton points found prior to our on-curve point, with the other curve being defined by all the inside skeleton points after our on-curve point.
<Graphic title="Splitting a curve" setup={this.setupCubic} draw={this.drawSplit} />
<div className="howtocode">
### implementing curve splitting
We can implement curve splitting by bolting some extra logging onto the de Casteljau function:
```
left=[]
right=[]
function drawCurve(points[], t):
if(points.length==1):
left.add(points[0])
right.add(points[0])
draw(points[0])
else:
newpoints=array(points.size-1)
for(i=0; i<newpoints.length; i++):
if(i==0):
left.add(points[i])
if(i==newpoints.length-1):
right.add(points[i+1])
newpoints[i] = (1-t) * points[i] + t * points[i+1]
drawCurve(newpoints, t)
```
After running this function for some value `t`, the `left` and `right` arrays will contain all the coordinates for two new curves - one to the "left" of our `t` value, the other on the "right", of the same order as the original curve, and overlayed exactly on the original curve.
</div>
This is best illustrated with an animated graphic (click to play/pause):
<Graphic preset="threepanel" title="Bézier curve splitting" setup={this.setupCubic} draw={this.drawAnimated} onClick={this.togglePlay} />

View File

@@ -1,11 +1,13 @@
var React = require("react"); var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx"); var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var page = "splitting";
var Splitting = React.createClass({ var Splitting = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
title: "Splitting curves" title: locale.getTitle(page)
}; };
}, },
@@ -101,50 +103,7 @@ var Splitting = React.createClass({
render: function() { render: function() {
return ( return (
<section> <section>{ locale.getContent(page, this) }</section>
<SectionHeader {...this.props} />
<p>With de Casteljau's algorithm we also find all the points we need to split up a Bézier curve into two, smaller
curves, which taken together form the original curve. When we construct de Casteljau's skeleton for some value
<i>t</i>, the procedure gives us all the points we need to split a curve at that <i>t</i> value: one curve is defined
by all the inside skeleton points found prior to our on-curve point, with the other curve being defined by all the
inside skeleton points after our on-curve point.</p>
<Graphic title="Splitting a curve" setup={this.setupCubic} draw={this.drawSplit} />
<div className="howtocode">
<h3>implementing curve splitting</h3>
<p>We can implement curve splitting by bolting some extra logging onto the de Casteljau function:</p>
<pre>left=[]
right=[]
function drawCurve(points[], t):
if(points.length==1):
left.add(points[0])
right.add(points[0])
draw(points[0])
else:
newpoints=array(points.size-1)
for(i=0; i<newpoints.length; i++):
if(i==0):
left.add(points[i])
if(i==newpoints.length-1):
right.add(points[i+1])
newpoints[i] = (1-t) * points[i] + t * points[i+1]
drawCurve(newpoints, t)</pre>
<p>After running this function for some value <i>t</i>, the <i>left</i> and <i>right</i> arrays
will contain all the coordinates for two new curves - one to the "left" of our <i>t</i> value,
the other on the "right", of the same order as the original curve, and overlayed exactly on the
original curve.</p>
</div>
<p>This is best illustrated with an animated graphic (click to play/pause):</p>
<Graphic preset="threepanel" title="Bézier curve splitting" setup={this.setupCubic} draw={this.drawAnimated} onClick={this.togglePlay} />
</section>
); );
} }
}); });

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.0 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,19 +1,19 @@
var enData = require('../locales/en-GB/content.js'); var enData = require('../locales/en-GB/content.js');
class Locale { var Locale = function(locale) {
constructor(locale) { this.data = {};
this.data = {}; this.locale = locale || "en-GB";
this.locale = locale || "en-GB"; this.data = enData;
this.data = enData; };
}
getContent(key, handler) { Locale.prototype = {
getContent: function(key, handler) {
return this.data[key].getContent(handler); return this.data[key].getContent(handler);
} },
getTitle(key) { getTitle: function(key) {
return this.data[key].title; return this.data[key].title;
} }
}; };
module.exports = Locale; module.exports = Locale;

View File

@@ -326,32 +326,826 @@ function Bezier(3,t,w[]):
}, },
"matrix": { "matrix": {
"title": "Unknown title (matrix)", "title": "Bézier curvatures as matrix operations",
"getContent": function(handler) { return <section> "getContent": function(handler) { return <section>
<SectionHeader name="matrix" title="Bézier curvatures as matrix operations" number="6"/>
<p>We can also represent Bézier as matrix operations, by expressing the Bézier formula as a polynomial basis function, the weight matrix, and the actual coordinates as matrix. Let's look at what this means for the cubic curve:</p>
\[
B(t) = P_1 \cdot (1-t)^3 + P_2 \cdot 3 \cdot (1-t)^2 \cdot t + P_3 \cdot 3 \cdot (1-t) \cdot t^2 + P_4 \cdot t^3
\]
<p>Disregarding our actual coordinates for a moment, we have:</p>
\[
B(t) = (1-t)^3 + 3 \cdot (1-t)^2 \cdot t + 3 \cdot (1-t) \cdot t^2 + t^3
\]
<p>We can write this as a sum of four expressions:</p>
\[
\begin{matrix}
... & = & (1-t)^3 \\
& + & 3 \cdot (1-t)^2 \cdot t \\
& + & 3 \cdot (1-t) \cdot t^2 \\
& + & t^3 \\
\end{matrix}
\]
<p>And we can expand these expressions:</p>
\[
\begin{matrix}
... & = & (1-t) \cdot (1-t) \cdot (1-t) & = & -t^3 + 3 \cdot t^2 - 3 \cdot t + 1 \\
& + & 3 \cdot (1-t) \cdot (1-t) \cdot t & = & 3 \cdot t^3 - 6 \cdot t^2 + 3 \cdot t \\
& + & 3 \cdot (1-t) \cdot t \cdot t & = & -3 \cdot t^3 + 3 \cdot t^2 \\
& + & t \cdot t \cdot t & = & t^3 \\
\end{matrix}
\]
<p>Furthermore, we can make all the 1 and 0 factors explicit:</p>
\[
\begin{matrix}
... & = & -1 \cdot t^3 + 3 \cdot t^2 - 3 \cdot t + 1 \\
& + & +3 \cdot t^3 - 6 \cdot t^2 + 3 \cdot t + 0 \\
& + & -3 \cdot t^3 + 3 \cdot t^2 + 0 \cdot t + 0 \\
& + & +1 \cdot t^3 + 0 \cdot t^2 + 0 \cdot t + 0 \\
\end{matrix}
\]
<p>And <em>that</em>, we can view as a series of four matrix operations:</p>
\[
\begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}-1 \\ 3 \\ -3 \\ 1\end{bmatrix}
+ \begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}3 \\ -6 \\ 3 \\ 0\end{bmatrix}
+ \begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}-3 \\ 3 \\ 0 \\ 0\end{bmatrix}
+ \begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}1 \\ 0 \\ 0 \\ 0\end{bmatrix}
\]
<p>If we compact this into a single matrix operation, we get:</p>
\[
\begin{bmatrix}t^3 & t^2 & t & 1\end{bmatrix} \cdot \begin{bmatrix}
-1 & 3 & -3 & 1 \\
3 & -6 & 3 & 0 \\
-3 & 3 & 0 & 0 \\
1 & 0 & 0 & 0
\end{bmatrix}
\]
<p>This kind of polynomial basis representation is generally written with the bases in increasing order, which means we need to flip our <code>t</code> matrix horizontally, and our big "mixing" matrix upside down:</p>
\[
\begin{bmatrix}1 & t & t^2 & t^3\end{bmatrix} \cdot \begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\]
<p>And then finally, we can add in our original coordinates as a single third matrix:</p>
\[
B(t) = \begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]
<p>We can perform the same trick for the quadratic curve, in which case we end up with:</p>
\[
B(t) = \begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
<p>If we plug in a <code>t</code> value, and then multiply the matrices, we will get exactly the same values as when we evaluate the original polynomial function, or as when we evaluate the curve using progessive linear interpolation.</p>
<p>
<strong>So: why would we bother with matrices?</strong> Matrix representations allow us to discover things about functions that would otherwise be hard to tell. It turns out that the curves form <a href="https://en.wikipedia.org/wiki/Triangular_matrix">triangular matrices</a>, and they have a determinant equal to the product of the actual coordinates we use for our curve. It's also invertible, which means there's <a href="https://en.wikipedia.org/wiki/Invertible_matrix#The_invertible_matrix_theorem">a ton of properties</a> that are all satisfied. Of course, the main question is: "Why is this useful to us, now?", and the answer to that is that it's not immediately useful, but you'll be seeing some instances where certain curve properties can be either computed via function manipulation, or via clever use of matrices, and sometimes the matrix approach can be (drastically) faster.</p>
<p>So for now, just remember that we can represent curves this way, and let's move on.</p>
</section>; } </section>; }
}, },
"decasteljau": { "decasteljau": {
"title": "Unknown title (decasteljau)", "title": "de Casteljau's algorithm",
"getContent": function(handler) { return <section> "getContent": function(handler) { return <section>
<SectionHeader name="decasteljau" title="de Casteljau's algorithm" number="7"/>
<p>If we want to draw Bézier curves we can run through all values of <code>t</code> from 0 to 1 and then compute the weighted basis function, getting the <code>x/y</code> values we need to plot, but the more complex the curve gets, the more expensive this becomes. Instead, we can use "de Casteljau's algorithm" to draw curves, which is a geometric approach to drawing curves, and really easy to implement. So easy, in fact, you can do it by hand with a pencil and ruler.</p>
<p>Rather than using our calculus function to find <code>x/y</code> values for <code>t</code>, let's do this instead:</p>
<ul>
<li>treat <code>t</code> as a ratio (which it is). t=0 is 0% along a line, t=1 is 100% along a line.</li>
<li>Take all lines between the curve's defining points. For an order <code>n</code> curve, that's <code>n</code> lines.</li>
<li>Place markers along each of these line, at distance <code>t</code>. So if <code>t</code> is 0.2, place the mark at 20% from the start, 80% from the end.</li>
<li>Now form lines between <code>those</code> points. This gives <code>n-1</code> lines.</li>
<li>Place markers along each of these line at distance <code>t</code>.</li>
<li>Form lines between <code>those</code> points. This'll be <code>n-2</code> lines.</li>
<li>place markers, form lines, place markers, etc.</li>
<li>repeat this until you have only one line left. The point <code>t</code> on that line coincides with the original curve point at <code>t</code>.</li>
</ul>
<div className="howtocode">
<h3 id="how-to-implement-de-casteljau-s-algorithm">How to implement de Casteljau's algorithm</h3>
<p>Let's just use the algorithm we just specified, and implement that:</p>
<pre>
<code>function drawCurve(points[], t):
if(points.length==1):
draw(points[0])
else:
newpoints=array(points.size-1)
for(i=0; i&lt;newpoints.length; i++):
newpoints[i] = (1-t) * points[i] + t * points[i+1]
drawCurve(newpoints, t)
</code>
</pre>
<p>And done, that's the algorithm implemented. Except usually you don't get the luxury of overloading the "+" operator, so let's also give the code for when you need to work with <code>x</code> and <code>y</code> values:</p>
<pre>
<code>function drawCurve(points[], t):
if(points.length==1):
draw(points[0])
else:
newpoints=array(points.size-1)
for(i=0; i&lt;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)
drawCurve(newpoints, t)
</code>
</pre>
<p>So what does this do? This draws a point, if the passed list of points is only 1 point long. Otherwise it will create a new list of points that sit at the <i>t</i> ratios (i.e. the "markers" outlined in the above algorithm), and then call the draw function for this new list.</p>
</div>
<p>To see this in action, mouse-over the following sketch. Moving the mouse changes which curve point is explicitly evaluated using de Casteljau's algorithm, moving the cursor left-to-right (or, of course, right-to-left), shows you how a curve is generated using this approach.</p>
<Graphic preset="simple"title="Traversing a curve using de Casteljau's algorithm" setup={handler.setup} draw={handler.draw}/>
</section>; } </section>; }
}, },
"flattening": { "flattening": {
"title": "Unknown title (flattening)", "title": "Simplified drawing",
"getContent": function(handler) { return <section> "getContent": function(handler) { return <section>
<SectionHeader name="flattening" title="Simplified drawing" number="8"/>
<p>We can also simplify the drawing process by "sampling" the curve at certain points, and then joining those points up with straight lines, a process known as "flattening", as we are reducing a curve to a simple sequence of straight, "flat" lines.</p>
<p>We can do this is by saying "we want X segments", and then sampling the curve at intervals that are spaced such that we end up with the number of segments we wanted. The advantage of this method is that it's fast: instead of evaluating 100 or even 1000 curve coordinates, we can sample a much lower number and still end up with a curve that sort-of-kind-of looks good enough. The disadvantage of course is that we lose the precision of working with "the real curve", so we usually can't use the flattened for for doing true intersection detection, or curvature alignment.</p>
<Graphic preset="twopanel" title="Flattening a quadratic curve" setup={handler.setupQuadratic} draw={handler.drawFlattened} onKeyDown={handler.onKeyDown}/>
<Graphic preset="twopanel" title="Flattening a cubic curve" setup={handler.setupCubic} draw={handler.drawFlattened} onKeyDown={handler.onKeyDown} />
<p>Try clicking on the sketch and using your up and down arrow keys to lower the number of segments for both the quadratic and cubic curve. You'll notice that for certain curvatures, a low number of segments works quite well, but for more complex curvatures (try this for the cubic curve), a higher number is required to capture the curvature changes properly.</p>
<div className="howtocode">
<h3 id="how-to-implement-curve-flattening">How to implement curve flattening</h3>
<p>Let's just use the algorithm we just specified, and implement that:</p>
<pre>
<code>function flattenCurve(curve, segmentCount):
step = 1/segmentCount;
coordinates = [curve.getXValue(0), curve.getYValue(0)]
for(i=1; i &lt;= segmentCount; i++):
t = i*step;
coordinates.push[curve.getXValue(t), curve.getYValue(t)]
return coordinates;
</code>
</pre>
<p>And done, that's the algorithm implemented. That just leaves drawing the resulting "curve" as a sequence of lines:</p>
<pre>
<code>function drawFlattenedCurve(curve, segmentCount):
coordinates = flattenCurve(curve, segmentCount)
coord = coordinates[0], _coords;
for(i=1; i &lt; coordinates.length; i++):
_coords = coordinates[i]
line(coords, _coords)
coords = _coords
</code>
</pre>
<p>We start with the first coordinate as reference point, and then just draw lines between each point and its next point.</p>
</div>
</section>; } </section>; }
}, },
"splitting": { "splitting": {
"title": "Unknown title (splitting)", "title": "Splitting curves",
"getContent": function(handler) { return <section> "getContent": function(handler) { return <section>
<SectionHeader name="splitting" title="Splitting curves" number="9"/>
<p>With de Casteljau's algorithm we also find all the points we need to split up a Bézier curve into two, smaller curves, which taken together form the original curve. When we construct de Casteljau's skeleton for some value <code>t</code>, the procedure gives us all the points we need to split a curve at that <code>t</code> value: one curve is defined by all the inside skeleton points found prior to our on-curve point, with the other curve being defined by all the inside skeleton points after our on-curve point.</p>
<Graphic title="Splitting a curve" setup={handler.setupCubic} draw={handler.drawSplit} />
<div className="howtocode">
<h3 id="implementing-curve-splitting">implementing curve splitting</h3>
<p>We can implement curve splitting by bolting some extra logging onto the de Casteljau function:</p>
<pre>
<code>left=[]
right=[]
function drawCurve(points[], t):
if(points.length==1):
left.add(points[0])
right.add(points[0])
draw(points[0])
else:
newpoints=array(points.size-1)
for(i=0; i&lt;newpoints.length; i++):
if(i==0):
left.add(points[i])
if(i==newpoints.length-1):
right.add(points[i+1])
newpoints[i] = (1-t) * points[i] + t * points[i+1]
drawCurve(newpoints, t)
</code>
</pre>
<p>After running this function for some value <code>t</code>, the <code>left</code> and <code>right</code> arrays will contain all the coordinates for two new curves - one to the "left" of our <code>t</code> value, the other on the "right", of the same order as the original curve, and overlayed exactly on the original curve.</p>
</div>
<p>This is best illustrated with an animated graphic (click to play/pause):</p>
<Graphic preset="threepanel" title="Bézier curve splitting" setup={handler.setupCubic} draw={handler.drawAnimated} onClick={handler.togglePlay} />
</section>; } </section>; }
}, },
"matrixsplit": { "matrixsplit": {
"title": "Unknown title (matrixsplit)", "title": "Splitting curves using matrices",
"getContent": function(handler) { return <section> "getContent": function(handler) { return <section>
<SectionHeader name="matrixsplit" title="Splitting curves using matrices" number="10"/>
<p>Another way to split curves is to exploit the matrix representation of a Bézier curve. In <a href="#matrix">the section on matrices</a> we saw that we can represent curves as matrix multiplications. Specifically, we saw these two forms for the quadratic, and cubic curves, respectively (using the reversed Bézier coefficients vector for legibility):</p>
\[
B(t) = \begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
<p>and</p>
\[
B(t) = \begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0\\
-3 & 3 & 0 & 0\\
3 & -6 & 3 & 0\\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]
<p>Let's say we want to split the curve at some point <code>t = z</code>, forming two new (obviously smaller) Bézier curves. To find the coordinates for these two Bézier curves, we can use the matrix representation and some linear algebra. First, we split out the the actual "point on the curve" information as a new matrix multiplication:</p>
\[
B(t) =
\begin{bmatrix}
1 & (z \cdot t) & (z \cdot t)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
0 & z & 0 \\
0 & 0 & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
<p>and</p>
\[
B(t) =
\begin{bmatrix}
1 & (z \cdot t) & (z \cdot t)^2 & (z \cdot t)^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0\\
0 & z & 0 & 0\\
0 & 0 & z^2 & 0\\
0 & 0 & 0 & z^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]
<p>If we could compact these matrices back to a form <strong>[t values] · [bezier matrix] · [column matrix]</strong>, with the first two staying the same, then that column matrix on the right would be the coordinates of a new Bézier curve that describes the first segment, from <code>t = 0</code> to <code>t = z</code>. As it turns out, we can do this quite easily, by exploiting some simple rules of linear algebra (and if you don't care about the derivations, just skip to the end of the box for the results!).</p>
<div className="note">
<h2 id="deriving-new-hull-coordinates">Deriving new hull coordinates</h2>
<p>Deriving the two segments upon splitting a curve takes a few steps, and the higher the curve order, the more work it is, so let's look at the quadratic curve first:</p>
\[
B(t) =
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
0 & z & 0 \\
0 & 0 & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
<p>[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\underset{we\ turn\ handler...}{\underbrace{\kern 2.25em Z \cdot M \kern 2.25em}}
\cdot
\begin{bmatrix}
P_1 \ P_2 \ P_3
\end{bmatrix}
]</p>
\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\underset{...into\ handler...}{\underbrace{ M \cdot M^{-1} \cdot Z \cdot M }}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
<p>[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
M
\underset{...to\ get\ \ this!}{\underbrace{ \kern 1.25em \cdot \kern 1.25em Q \kern 1.25em \cdot \kern 1.25em}}
\begin{bmatrix}
P_1 \ P_2 \ P_3
\end{bmatrix}
]</p>
<p>We do this, because [<em>M · M<sup>-1</sup>
</em>] is the identity matrix (a bit like multiplying something by x/x in calculus. It doesn't do anything to the function, but it does allow you to rewrite it to something that may be easier to work with, or can be broken up differently). Adding that as matrix multiplication has no effect on the total formula, but it does allow us to change the matrix sequence [<em>something · M</em>] to a sequence [<em>M · something</em>], and that makes a world of difference: if we know what [<em>M<sup>-1</sup> · Z · M</em>] is, we can apply that to our coordinates, and be left with a proper matrix representation of a quadratic Bézier curve (which is [<em>T · M · P</em>]), with a new set of coordinates that represent the curve from <em>t = 0</em> to <em>t = z</em>. So let's get computing:</p>
\[
Q = M^{-1} \cdot Z \cdot M =
\begin{bmatrix}
1 & 0 & 0 \\
1 & \frac{1}{2} & 0 \\
1 & 1 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
0 & z & 0 \\
0 & 0 & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
=
\begin{bmatrix}
1 & 0 & 0 \\
-(z-1) & z & 0 \\
(z - 1)^2 & -2 \cdot (z-1) \cdot z & z^2
\end{bmatrix}
\]
<p>Excellent! Now we can form our new quadratic curve:</p>
\[
B(t) =
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot M \cdot Q \cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
M
\cdot
\left (
Q
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\right )
\]
<p>[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \
-2 & 2 & 0 \
1 & -2 & 1
\end{bmatrix}
\cdot
\left (
\begin{bmatrix}
1 & 0 & 0 \
-(z-1) & z & 0 \
(z - 1)^2 & -2 \cdot (z-1) \cdot z & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \ P_2 \ P_3
\end{bmatrix}
\right )
]</p>
\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\
z \cdot P_2 - (z-1) \cdot P_1 \\
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z - 1)^2 \cdot P_1
\end{bmatrix}
\]
<p>
<strong>
<em>Brilliant</em>
</strong>: if we want a subcurve from <code>t = 0</code> to <code>t = z</code>, we can keep the first coordinate the same (which makes sense), our control point becomes a z-ratio mixture of the original control point and the start point, and the new end point is a mixture that looks oddly similar to a bernstein polynomial of degree two, except it uses (z-1) rather than (1-z)... These new coordinates are actually really easy to compute directly!</p>
<p>Of course, that's only one of the two curves. Getting the section from <code>t = z</code> to <code>t = 1</code> requires doing this again. We first observe what what we just did is actually evaluate the general interval [0,<code>z</code>], which we wrote down simplified becuase of that zero, but we actually evaluated this:</p>
\[
B(t) =
\begin{bmatrix}
1 & ( 0 + z \cdot t) & ( 0 + z \cdot t)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
<p>[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \
0 & z & 0 \
0 & 0 & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \
-2 & 2 & 0 \
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \ P_2 \ P_3
\end{bmatrix}
]</p>
<p>If we want the interval [<em>z</em>,1], we will be evaluating this instead:</p>
\[
B(t) =
\begin{bmatrix}
1 & ( z + (1-z) \cdot t) & ( z + (1-z) \cdot t)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\]
<p>[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & z & z^2 \
0 & 1-z & 2 \cdot z \cdot (1-z) \
0 & 0 & (1-z)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \
-2 & 2 & 0 \
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \ P_2 \ P_3
\end{bmatrix}
]</p>
<p>We're going to do the same trick, to turn <code>[something · M]</code> into <code>[M · something]</code>:</p>
\[
Q' = M^{-1} \cdot Z' \cdot M =
\begin{bmatrix}
1 & 0 & 0 \\
1 & \frac{1}{2} & 0 \\
1 & 1 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & z & z^2 \\
0 & 1-z & 2 \cdot z \cdot (1-z) \\
0 & 0 & (1-z)^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
=
\begin{bmatrix}
(z-1)^2 & -2 \cdot z \cdot (z-1) & z^2 \\
0 & -(z-1) & z \\
0 & 0 & 1
\end{bmatrix}
\]
<p>So, our final second curve looks like:</p>
\[
B(t) =
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot M \cdot Q \cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
M
\cdot
\left (
Q'
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
\right )
\]
<p>[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \
-2 & 2 & 0 \
1 & -2 & 1
\end{bmatrix}
\cdot
\left (
\begin{bmatrix}
(z-1)^2 & -2 \cdot z \cdot (z-1) & z^2 \
0 & -(z-1) & z \
0 & 0 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \ P_2 \ P_3
\end{bmatrix}
\right )
]</p>
\[
=
\begin{bmatrix}
1 & t & t^2
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z-1)^2 \cdot P_1 \\
z \cdot P_3 - (z-1) \cdot P_2 \\
P_3
\end{bmatrix}
\]
<p>
<strong>
<em>Nice</em>
</strong>: we see the same as before; can keep the last coordinate the same (which makes sense), our control point becomes a z-ratio mixture of the original control point and the end point, and the new start point is a mixture that looks oddly similar to a bernstein polynomial of degree two, except it uses (z-1) rather than (1-z). These new coordinates are <em>also</em> really easy to compute directly!</p>
</div>
<p>So, using linear algebra rather than de Casteljau's algorithm, we have determined that for any quadratic curve split at some value <code>t = z</code>, we get two subcurves that are described as Bézier curves with simple-to-derive coordinates.</p>
\[
\begin{bmatrix}
1 & 0 & 0 \\
-(z-1) & z & 0 \\
(z - 1)^2 & -2 \cdot (z-1) \cdot z & z^2
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
P_1 \\
z \cdot P_2 - (z-1) \cdot P_1 \\
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z - 1)^2 \cdot P_1
\end{bmatrix}
\]
<p>and</p>
\[
\begin{bmatrix}
(z-1)^2 & -2 \cdot z \cdot (z-1) & z^2 \\
0 & -(z-1) & z \\
0 & 0 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3
\end{bmatrix}
=
\begin{bmatrix}
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z-1)^2 \cdot P_1 \\
z \cdot P_3 - (z-1) \cdot P_2 \\
P_3
\end{bmatrix}
\]
<p>We can do the same for cubic curves. However, I'll spare you the actual derivation (don't let that stop you from writing that out yourself, though) and simply show you the resulting new coordinate sets:</p>
\[
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-(z-1) & z & 0 & 0 \\
(z-1)^2 & -2 \cdot (z-1) \cdot z & z^2 & 0 \\
-(z-1)^3 & 3 \cdot (z-1)^2 \cdot z & -3 \cdot (z-1) \cdot z^2 & z^3
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
P_1 \\
z \cdot P_2 - (z-1) \cdot P_1 \\
z^2 \cdot P_3 - 2 \cdot z \cdot (z-1) \cdot P_2 + (z-1)^2 \cdot P_1 \\
z^3 \cdot P_4 - 3 \cdot z^2 \cdot (z-1) \cdot P_3 + 3 \cdot z \cdot (z-1)^2 \cdot P_2 - (z-1)^3 \cdot P_1
\end{bmatrix}
\]
<p>and</p>
\[
\begin{bmatrix}
-(z-1)^3 & 3 \cdot (z-1)^2 \cdot z & -3 \cdot (z-1)^3 \cdot z^2 & z^3 \\
0 & (z-1)^2 & -2 \cdot (z-1) \cdot z & z^2 \\
0 & 0 & -(z-1) & z \\
0 & 0 & 0 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
z^3 \cdot P_4 - 3 \cdot z^2 \cdot (z-1) \cdot P_3 + 3 \cdot z \cdot (z-1)^2 \cdot P_2 - (z-1)^3 \cdot P_1 \\
z^2 \cdot P_4 - 2 \cdot z \cdot (z-1) \cdot P_3 + (z-1)^2 \cdot P_2 \\
z \cdot P_4 - (z-1) \cdot P_3 \\
P_4
\end{bmatrix}
\]
<p>So, looking at our matrices, did we really need to compute the second segment matrix? No, we didn't. Actually having one segment's matrix means we implicitly have the other: push the values of each row in the matrix <strong>
<em>Q</em>
</strong> to the right, with zeroes getting pushed off the right edge and appearing back on the left, and then flip the matrix vertically. Presto, you just "calculated" <strong>
<em>Q'</em>
</strong>.</p>
<p>Implementing curve splitting this way requires less recursion, and is just straight arithmetic with cached values, so can be cheaper on systems were recursion is expensive. If you're doing computation with devices that are good at matrix multiplication, chopping up a Bézier curve with this method will be a lot faster than applying de Casteljau.</p>
</section>; } </section>; }
}, },

View File

@@ -161,16 +161,16 @@ sections.forEach((cname, number) => {
if (!block.convert) return block.data; if (!block.convert) return block.data;
// markdown conversion is a little more work // markdown conversion is a little more work
let d = marked(block.data.trim()); let d = marked(block.data.trim());
// And then some post-processing... // serious can we fucking not, please.
d = d.replace('<p></div></p>', '</div>')
.replace(/&amp;/g, '&')
.replace(/&#39;/g, "'")
.replace(/&quot;/g, '"')
// And then title extraction/rewriting
d = d.replace(/<h1[^>]+>([^<]+)<\/h1>/,function(_,t) { d = d.replace(/<h1[^>]+>([^<]+)<\/h1>/,function(_,t) {
title = t; title = t;
return `<SectionHeader name="${cname}" title="` + t + `"${ number ? ' number="'+number+'"': ''}/>`; return `<SectionHeader name="${cname}" title="` + t + `"${ number ? ' number="'+number+'"': ''}/>`;
}); });
d = d.replace('<p></div></p>', '</div>')
// serious can we fucking not, please.
.replace(/&#39;/g, "'")
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
return d; return d;
}).join(''); }).join('');
} catch (e) { } catch (e) {

View File

@@ -3,8 +3,9 @@
"version": "1.0.0", "version": "1.0.0",
"description": "pomax.github.io/bezierinfo", "description": "pomax.github.io/bezierinfo",
"scripts": { "scripts": {
"build": "npm run less && webpack --prod", "localize": "node make-locales",
"dev": "node make-locales && npm run less && webpack-dev-server --progress --colors --hot --inline", "build": "npm run localize && npm run less && webpack -p",
"dev": "npm run localize && npm run less && webpack-dev-server --progress --colors --hot --inline",
"latex": "node tools/mathjax", "latex": "node tools/mathjax",
"less": "lessc stylesheets/style.less > stylesheets/style.css", "less": "lessc stylesheets/style.less > stylesheets/style.css",
"singles": "npm run dev -- --singles", "singles": "npm run dev -- --singles",

View File

@@ -20,22 +20,10 @@ var webpackLoaders = [
'eslint-loader', 'eslint-loader',
__dirname + '/lib/latex-loader', __dirname + '/lib/latex-loader',
__dirname + '/lib/pre-loader', __dirname + '/lib/pre-loader',
__dirname + '/lib/p-loader' __dirname + '/lib/p-loader',
__dirname + '/lib/textarea-loader'
]; ];
var plugins = [];
// Dev mode: make certain concessions to speed up dev work.
if(process.argv.indexOf("--prod") === -1 && process.argv.indexOf("--lint")) {
// allow code in textareas when in dev mode:
webpackLoaders.push(__dirname + '/lib/textarea-loader');
}
// Prod mode: make sure to minify the bundle
else if(process.argv.indexOf("--prod") > -1) {
plugins.push(new webpack.optimize.UglifyJsPlugin());
}
console.log("content for entry:", entry); console.log("content for entry:", entry);
// And the final config that webpack will read in. // And the final config that webpack will read in.
@@ -67,6 +55,5 @@ module.exports = {
loaders: webpackLoaders loaders: webpackLoaders
} }
] ]
}, }
plugins: plugins
}; };