1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-29 19:20:39 +02:00

localizing is now pretty nice

This commit is contained in:
Pomax
2017-02-18 13:45:26 -08:00
parent 0fda4e7ab0
commit 4808a59357
53 changed files with 3428 additions and 1748 deletions

View File

@@ -0,0 +1,44 @@
# Aligning curves
While there are an incredible number of curves we can define by varying the x- and y-coordinates for the control points, not all curves are actually distinct. For instance, if we define a curve, and then rotate it 90 degrees, it's still the same curve, and we'll find its extremities in the same spots, just at different draw coordinates. As such, one way to make sure we're working with a "unique" curve is to "axis-align" it.
Aligning also simplifies a curve's functions. We can translate (move) the curve so that the first point lies on (0,0), which turns our *n* term polynomial functions into *n-1* term functions. The order stays the same, but we have less terms. Then, we can rotate the curves so that the last point always lies on the x-axis, too, making its coordinate (...,0). This further simplifies the function for the y-component to an *n-2* term function. For instance, if we have a cubic curve such as this:
\[
\left \{ \begin{matrix}
x = BLUE[120] \cdot (1-t)^3 BLUE[+ 35] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[+ 220] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[+ 220] \cdot t^3 \\
y = BLUE[160] \cdot (1-t)^3 BLUE[+ 200] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[+ 260] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[+ 40] \cdot t^3
\end{matrix} \right.
\]
Then translating it so that the first coordinate lies on (0,0), moving all *x* coordinates by -120, and all *y* coordinates by -160, gives us:
\[
\left \{ \begin{matrix}
x = BLUE[0] \cdot (1-t)^3 BLUE[- 85] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[+ 100] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[+ 100] \cdot t^3 \\
y = BLUE[0] \cdot (1-t)^3 BLUE[+ 40] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[+ 100] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[- 120] \cdot t^3
\end{matrix} \right.
\]
If we then rotate the curve so that its end point lies on the x-axis, the coordinates (integer-rounded for illustrative purposes here) become:
\[
\left \{ \begin{matrix}
x = BLUE[0] \cdot (1-t)^3 BLUE[+ 85] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[+ 12] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[- 156] \cdot t^3 \\
y = BLUE[0] \cdot (1-t)^3 BLUE[+ 40] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[- 140] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[+ 0] \cdot t^3
\end{matrix} \right.
\]
If we drop all the zero-terms, this gives us:
\[
\left \{ \begin{array}{l}
x = BLUE[85] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[+ 13] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[- 156] \cdot t^3 \\
y = BLUE[40] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[- 141] \cdot 3 \cdot (1-t) \cdot t^2
\end{array} \right.
\]
We can see that our original curve definition has been simplified considerably. The following graphics illustrate the result of aligning our example curves to the x-axis, with the cubic case using the coordinates that were just used in the example formulae:
<Graphic preset="twopanel" title="Aligning a quadratic curve" setup={this.setupQuadratic} draw={this.draw} />
<Graphic preset="twopanel" title="Aligning a cubic curve" setup={this.setupCubic} draw={this.draw} />

View File

@@ -1,11 +1,13 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "aligning";
var Aligning = React.createClass({
getDefaultProps: function() {
return {
title: "Aligning curves"
title: locale.getTitle(page)
};
},
@@ -62,67 +64,8 @@ var Aligning = React.createClass({
api.drawCurve(aligned, offset);
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>While there are an incredible number of curves we can define by varying the x- and y-coordinates for
the control points, not all curves are actually distinct. For instance, if we define a curve, and then
rotate it 90 degrees, it's still the same curve, and we'll find its extremities in the same spots, just
at different draw coordinates. As such, one way to make sure we're working with a "unique" curve is to
"axis-align" it.</p>
<p>Aligning also simplifies a curve's functions. We can translate (move) the curve so that the first
point lies on (0,0), which turns our <i>n</i> term polynomial functions into <i>n-1</i> term functions.
The order stays the same, but we have less terms. Then, we can rotate the curves so that the last point
always lies on the x-axis, too, making its coordinate (...,0). This further simplifies the function for
the y-component to an <i>n-2</i> term function. For instance, if we have a cubic curve such as this:</p>
<p>\[
\left \{ \begin{matrix}
x = BLUE[120] \cdot (1-t)^3 BLUE[+ 35] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[+ 220] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[+ 220] \cdot t^3 \\
y = BLUE[160] \cdot (1-t)^3 BLUE[+ 200] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[+ 260] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[+ 40] \cdot t^3
\end{matrix} \right. \]</p>
<p>Then translating it so that the first coordinate lies on (0,0), moving all <i>x</i> coordinates
by -120, and all <i>y</i> coordinates by -160, gives us:</p>
<p>\[
\left \{ \begin{matrix}
x = BLUE[0] \cdot (1-t)^3 BLUE[- 85] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[+ 100] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[+ 100] \cdot t^3 \\
y = BLUE[0] \cdot (1-t)^3 BLUE[+ 40] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[+ 100] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[- 120] \cdot t^3
\end{matrix} \right. \]</p>
<p>If we then rotate the curve so that its end point lies on the x-axis, the coordinates (integer-rounded
for illustrative purposes here) become:</p>
<p>\[
\left \{ \begin{matrix}
x = BLUE[0] \cdot (1-t)^3 BLUE[+ 85] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[+ 12] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[- 156] \cdot t^3 \\
y = BLUE[0] \cdot (1-t)^3 BLUE[+ 40] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[- 140] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[+ 0] \cdot t^3
\end{matrix} \right. \]</p>
<p>If we drop all the zero-terms, this gives us:</p>
<p>\[
\left \{ \begin{array}{l}
x = BLUE[85] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[+ 13] \cdot 3 \cdot (1-t) \cdot t^2 BLUE[- 156] \cdot t^3 \\
y = BLUE[40] \cdot 3 \cdot (1-t)^2 \cdot t BLUE[- 141] \cdot 3 \cdot (1-t) \cdot t^2
\end{array} \right. \]</p>
<p>We can see that our original curve definition has been simplified considerably. The following graphics
illustrate the result of aligning our example curves to the x-axis, with the cubic case using
the coordinates that were just used in the example formulae:</p>
<Graphic preset="twopanel" title="Aligning a quadratic curve" setup={this.setupQuadratic} draw={this.draw} />
<Graphic preset="twopanel" title="Aligning a cubic curve" setup={this.setupCubic} draw={this.draw} />
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -0,0 +1,16 @@
# Bounding boxes
If we have the extremities, and the start/end points, a simple for loop that tests for min/max values for x and y means we have the four values we need to box in our curve:
*Computing the bounding box for a Bézier curve*:
1. Find all *t* value(s) for the curve derivative's x- and y-roots.
2. Discard any *t* value that's lower than 0 or higher than 1, because Bézier curves only use the interval [0,1].
3. Determine the lowest and highest value when plugging the values *t=0*, *t=1* and each of the found roots into the original functions: the lowest value is the lower bound, and the highest value is the upper bound for the bounding box we want to construct.
Applying this approach to our previous root finding, we get the following bounding boxes (with all curve extremity points shown on the curve):
<Graphic preset="simple" title="Quadratic Bézier bounding box" setup={this.setupQuadratic} draw={this.draw} />
<Graphic preset="simple" title="Cubic Bézier bounding box" setup={this.setupCubic} draw={this.draw} />
We can construct even nicer boxes by aligning them along our curve, rather than along the x- and y-axis, but in order to do so we first need to look at how aligning works.

View File

@@ -1,11 +1,13 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "boundingbox";
var BoundingBox = React.createClass({
getDefaultProps: function() {
return {
title: "Bounding boxes"
title: locale.getTitle(page)
};
},
@@ -26,38 +28,14 @@ var BoundingBox = React.createClass({
api.setColor("black");
api.drawSkeleton(curve);
api.drawCurve(curve);
api.setColor("red");
curve.extrema().values.forEach(t => {
api.drawCircle(curve.get(t), 3);
});
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>If we have the extremities, and the start/end points, a simple for loop that tests for min/max values for
x and y means we have the four values we need to box in our curve:</p>
<p><i>Computing the bounding box for a Bézier curve</i>:</p>
<ol>
<li>Find all <i>t</i> value(s) for the curve derivative's x- and y-roots.</li>
<li>Discard any <i>t</i> value that's lower than 0 or higher than 1, because Bézier curves only use the interval [0,1].</li>
<li>Determine the lowest and highest value when plugging the values <i>t=0</i>, <i>t=1</i> and each of the found
roots into the original functions: the lowest value is the lower bound, and the highest value is the upper
bound for the bounding box we want to construct.</li>
</ol>
<p>Applying this approach to our previous root finding, we get the following bounding boxes (with curve
extremities coloured the same as in the root finding graphics):</p>
<Graphic preset="simple" title="Quadratic Bézier bounding box" setup={this.setupQuadratic} draw={this.draw} />
<Graphic preset="simple" title="Cubic Bézier bounding box" setup={this.setupCubic} draw={this.draw} />
<p>We can construct even nicer boxes by aligning them along our curve, rather than along the x- and y-axis,
but in order to do so we first need to look at how aligning works.</p>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -0,0 +1,283 @@
# Canonical form (for cubic curves)
While quadratic curves are relatively simple curves to analyze, the same cannot be said of the cubic curve. As a curvature controlled by more than one control points, it exhibits all kinds of features like loops, cusps, odd colinear features, and up to two inflection points because the curvature can change direction up to three times. Now, knowing what kind of curve we're dealing with means that some algorithms can be run more efficiently than if we have to implement them as generic solvers, so is there a way to determine the curve type without lots of work?
As it so happens, the answer is yes and the solution we're going to look at was presented by Maureen C. Stone from Xerox PARC and Tony D. deRose from the University of Washington in their joint paper ["A Geometric Characterization of Parametric Cubic curves"](http://graphics.pixar.com/people/derose/publications/CubicClassification/paper.pdf). It was published in 1989, and defines curves as having a "canonical" form (i.e. a form that all curves can be reduced to) from which we can immediately tell which features a curve will have. So how does it work?
The first observation that makes things work is that if we have a cubic curve with four points, we can apply a linear transformation to these points such that three of the points end up on (0,0), (0,1) and (1,1), with the last point then being "somewhere". After applying that transformation, the location of that last point can then tell us what kind of curve we're dealing with. Specifically, we see the following breakdown:
<Graphic static={true} preset="simple" title="The canonical curve map" setup={this.setup} draw={this.drawBase} />
This is a fairly funky image, so let's see how it breaks down. We see the three fixed points at (0,0), (0,1) and (1,1), and then the fourth point is somewhere. Depending on where it is, our curve will have certain features. Namely, if the fourth point is...
1. anywhere on and in the red zone, the curve will be self-intersecting, yielding either a cusp or a loop. Anywhere inside the the red zone, this will be a loop. We won't know <i>where</i> that loop is (in terms of <i>t</i> values), but we are guaranteed that there is one.
2. on the left (red) edge, the curve will have a cusp. We again don't know <em>where</em>, just that it
has one. This edge is described by the function:
\[
y = \frac{-x^2 + 2x + 3}{4}, \{ x \leq 1 \}
\]
3. on the lower right (pink) edge, the curve will have a loop at t=1, so we know the end coordinate of
the curve also lies <em>on</em> the curve. This edge is described by the function:
\[
y = \frac{\sqrt{3(4x - x^2)} - x}{2}, \{ 0 \leq x \leq 1 \}
\]
4. on the top (blue) edge, the curve will have a loop at t=0, so we know the start coordinate of
the curve also lies <em>on</em> the curve. This edge is described by the function:
\[
y = \frac{-x^2 + 3x}{3}, \{ x \leq 0 \}
\]
5. inside the green zone, the curve will have a single inflection, switching concave/convex once.
6. between the red and green zones, the curve has two inflections, meaning its curvature switches between
concave/convex form twice.
7. anywhere on the right of the red zone, the curve will have no inflections. It'll just be a well-behaved arch.
Of course, this map is fairly small, but the regions extend to infinity, with well defined boundaries.
<div className="note">
### Wait, where do those lines come from?
Without repeating the paper mentioned at the top of this section, the loop-boundaries come from rewriting the curve into canonical form, and then solving the formulae for which constraints must hold for which possible curve properties. In the paper these functions yield formulae for where you will find cusp points, or loops where we know t=0 or t=1, but those functions are derived for the full cubic expression, meaning they apply to t=-∞ to t=∞... For Bézier curves we only care about the "clipped interval" t=0 to t=1, so some of the properties that apply when you look at the curve over an infinite interval simply don't apply to the Bézier curve interval.
The right bound for the loop region, indicating where the curve switches from "having inflections" to "having a loop", for the general cubic curve, is actually mirrored over x=1, but for Bézier curves this right half doesn't apply, so we don't need to pay attention to it. Similarly, the boundaries for t=0 and t=1 loops are also nice clean curves but get "cut off" when we only look at what the general curve does over the interval t=0 to t=1.
For the full details, head over to the paper and read through sections 3 and 4. If you still remember your high school precalculus, you can probably follow along with this paper, although you might have to read it a few times before all the bits "click".
</div>
So now the question becomes: how do we manipulate our curve so that it fits this canonical form, with three fixed points, and one "free" point? Enter linear algerba. Don't worry, I'll be doing all the math for you, as well as show you what the effect is on our curves, but basically we're going to be using linear algebra, rather than calculus, because "it's way easier". Sometimes a calculus approach is very hard to work with, when the equivalent geometrical solution is super obvious.
The approach is going to start with a curve that doesn't have all-colinear points (so we need to make sure the points don't all fall on a straight line), and then applying four graphics operations that you will probably have heard of: translation (moving all points by some fixed x- and y-distance), scaling (multiplying all points by some x and y scale factor), and shearing (an operation that turns rectangles into parallelograms).
Step 1: we translate any curve by -p1.x and -p1.y, so that the curve starts at (0,0). We're going to make use of an interesting trick here, by pretending our 2D coordinates are 3D, with the <i>z</i> coordinate simply always being 1. This is an old trick in graphics to overcome the limitations of 2D transformations: without it, we can only turn (x,y) coordinates into new coordinates of the form (ax + by, cx + dy), which means we can't do translation, since that requires we end up with some kind of (x + a, y + b). If we add a bogus <i>z</i> coordinate that is always 1, then we can suddenly add arbitrary values. For example:
\[
\left [ \begin{array}
01 & 0 & a \\
0 & 1 & b \\
0 & 0 & 1
\end{array} \right ]
\cdot
\left [
\begin{matrix}
x \\
y \\
z=1
\end{matrix}
\right ]
=
\left [
\begin{matrix}
1 \cdot x + 0 \cdot y + a \cdot z \\
0 \cdot x + 1 \cdot y + b \cdot z \\
0 \cdot x + 0 \cdot y + 1 \cdot z
\end{matrix}
\right ]
=
\left [
\begin{matrix}
x + a \cdot 1 \\
y + b \cdot 1 \\
1 \cdot z
\end{matrix}
\right ]
=
\left [
\begin{matrix}
x + a \\
y + b \\
z=1
\end{matrix}
\right ]
\]
Sweet! <i>z</i> stays 1, so we can effectively ignore it entirely, but we added some plain values to our x and y coordinates. So, if we want to subtract p1.x and p1.y, we use:
\[
T_1 =
\left [ \begin{array}
01 & 0 & -{P_1}_x \\
0 & 1 & -{P_1}_y \\
0 & 0 & 1
\end{array} \right ]
\cdot
\left [
\begin{matrix}
x \\
y \\
1
\end{matrix}
\right ]
=
\left [
\begin{matrix}
1 \cdot x + 0 \cdot y - {P_1}_x \cdot 1 \\
0 \cdot x + 1 \cdot y - {P_1}_y \cdot 1 \\
0 \cdot x + 0 \cdot y + 1 \cdot 1
\end{matrix}
\right ]
=
\left [
\begin{matrix}
x - {P_1}_x \\
y - {P_1}_y \\
1
\end{matrix}
\right ]
\]
Running all our coordinates through this transformation gives a new set of coordinates, let's call those <b>U</b>, where the first coordinate lies on (0,0), and the rest is still somewhat free. Our next job is to make sure point 2 ends up lying on the <i>x=0</i> line, so what we want is a transformation matrix that, when we run it, subtracts <i>x</i> from whatever <i>x</i> we currently have. This is called [shearing](https://en.wikipedia.org/wiki/Shear_matrix), and the typical x-shear matrix and its transformation looks like this:
\[
\left [
\begin{matrix}
1 & S & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{matrix}
\right ]
\cdot
\left [
\begin{matrix}
x \\
y \\
1
\end{matrix}
\right ]
=
\left [
\begin{matrix}
x + S \cdot y \\
y \\
1
\end{matrix}
\right ]
\]
So we want some shearing value that, when multiplied by <i>y</i>, yields <i>-x</i>, so our x coordinate becomes zero. That value is simpy <i>-x/y</i>, because <i>-x/y * y = -x</i>. Done:
\[
T_2 =
\left [
\begin{matrix}
1 & -\frac{ {U_2}_x }{ {U_2}_y } & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{matrix}
\right ]
\]
Now, running this on all our points generates a new set of coordinates, let's call those V, which now have point 1 on (0,0) and point 2 on (0, some-value), and we wanted it at (0,1), so we need to [do some scaling](https://en.wikipedia.org/wiki/Scaling_%28geometry%29) to make sure it ends up at (0,1). Additionally, we want point 3 to end up on (1,1), so we can also scale x to make sure its x-coordinate will be 1 after we run the transform. That means we'll be x-scaling by 1/point3<sub>x</sub>, and y-scaling by point2<sub>y</sub>. This is really easy:
\[
T_3 =
\left [
\begin{matrix}
\frac{1}{ {V_3}_x } & 0 & 0 \\
0 & \frac{1}{ {V_2}_y } & 0 \\
0 & 0 & 1
\end{matrix}
\right ]
\]
Then, finally, this generates a new set of coordinates, let's call those W, of which point 1 lies on (0,0), point 2 lies on (0,1), and point three lies on (1, ...) so all that's left is to make sure point 3 ends up at (1,1) - but we can't scale! Point 2 is already in the right place, and y-scaling would move it out of (0,1) again, so our only option is to y-shear point three, just like how we x-sheared point 2 earlier. In this case, we do the same trick, but with `y/x` rather than `x/y` because we're not x-shearing but y-shearing. Additionally, we don't actually want to end up at zero (which is what we did before) so we need to shear towards an offset, in this case 1:
\[
T_4 =
\left [
\begin{matrix}
1 & 0 & 0 \\
\frac{1 - {W_3}_y}{ {W_3}_x } & 1 & 0 \\
0 & 0 & 1
\end{matrix}
\right ]
\]
And this generates our final set of four coordinates. Of these, we already know that points 1 through 3 are (0,0), (0,1) and (1,1), and only the last coordinate is "free". In fact, given any four starting coordinates, the resulting "transformation mapped" coordinate will be:
\[
mapped_4 = \left (
\begin{matrix}
x = \left (
\frac
{
-x_1 + x_4 - \frac{(-x_1+x_2)(-y_1+y_4)}{-y_1+y_2}
}
{
-x_1+x_3-\frac{(-x_1+x_2)(-y_1+y_3)}{-y_1+y_2}
}
\right )
\\
y = \left (
\frac{(-y_1+y_4)}{-y_1+y_2}
+
\frac
{
\left ( 1 - \frac{-y_1+y_3}{-y_1+y_2} \right )
\left ( -x_1 + x_4 - \frac{(-x_1+x_2)(-y_1+y_4)}{-y_1+y_2} \right )
}
{
-x_1+x_3-\frac{(-x_1+x_2)(-y_1+y_3)}{-y_1+y_2}
}
\right )
\end{matrix}
\right )
\]
That looks very complex, but notice that every coordinate value is being offset by the initial translation, and a lot of terms in there repeat: it's pretty easy to calculate this fast, since there's so much we can cache and reuse while we compute this mapped coordinate!
First, let's just do that translation step as a "preprocessing" operation so we don't have to subtract the values all the time. What does that leave?
\[
... = \left (
\begin{matrix}
x = \left ( x_4 - \frac{x_2 \cdot y_4}{y_2} \middle/ x_3-\frac{x_2 \cdot y_3}{y_2} \right )
\\
y =
\frac{y_4}{y_2}
+
\left ( 1 - \frac{y_3}{y_2} \right )
\cdot
\left ( x_4 - \frac{x_2 \cdot y_4}{y_2} \middle/ x_3-\frac{x_2 \cdot y_3}{y_2} \right )
\end{matrix}
\right )
\]
Suddenly things look a lot simpler: the mapped x is fairly straight forward to compute, and we see that the mapped y actually contains the mapped x in its entirety, so we'll have that part already available when we need to evaluate it. In fact, let's pull out all those common factors to see just how simple this is:
\[
... = \left (
\begin{matrix}
x = (x_4 - x_2 \cdot f_{42}) / ( x_3- x_2 \cdot f_{32} )
\\
y =
f_{42}
+
\left ( 1 - f_{32} \right )
\cdot
x
\end{matrix}
\right ), f_{32} = \frac{y_3}{y_2}, f_{42} = \frac{y_4}{y_2}
\]
That's kind of super-simple to write out in code, I think you'll agree. Coding math tends to be easier than the formulae initially make it look!
<div className="note">
### How do you track all that?
Doing maths can be a pain, so whenever possible, I like to make computers do the work for me. Especially for things like this, I simply use [Mathematica](http://www.wolfram.com/mathematica). Tracking all this math by hand is insane, and we invented computers, literally, to do this for us. I have no reason to use pen and paper when I can write out what I want to do in a program, and have the program do the math for me. And real math, too, with symbols, not with numbers. In fact, [here's](http://pomax.github.io/gh-weblog/downloads/canonical-curve.nb) the Mathematica notebook if you want to see how this works for yourself.
Now, I know, you're thinking "but Mathematica is super expensive!" and that's true, it's [$295 for home use](http://www.wolfram.com/mathematica-home-edition), but it's **also** [free when you buy a $35 raspberry pi](http://www.wolfram.com/raspberry-pi). Obviously, I bought a raspberry pi, and I encourage you to do the same. With that, as long as you know what you want to *do*, Mathematica can just do it for you. And we don't have to be geniusses to work out what the maths looks like. That's what we have computers for.
</div>
So, let's write up a sketch that'll show us the canonical form for any curve drawn in blue, overlaid on our canonical map, so that we can immediately tell which features our curve must have, based on where the fourth coordinate is located on the map:
<Graphic preset="simple" title="A cubic curve mapped to canonical form" setup={this.setup} draw={this.draw} />

View File

@@ -1,11 +1,13 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "canonical";
var Canonical = React.createClass({
getDefaultProps: function() {
return {
title: "Canonical form (for cubic curves)"
title: locale.getTitle(page)
};
},
@@ -167,329 +169,8 @@ var Canonical = React.createClass({
api._map_loaded = true;
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>While quadratic curves are relatively simple curves to analyze, the same cannot be said of the cubic curve.
As a curvature controlled by more than one control points, it exhibits all kinds of features like loops,
cusps, odd colinear features, and up to two inflection points because the curvature can change direction
up to three times. Now, knowing what kind of curve we're dealing with means that some algorithms can be
run more efficiently than if we have to implement them as generic solvers, so is there a way to determine
the curve type without lots of work?</p>
<p>As it so happens, the answer is yes and the solution we're going to look at was presented by Maureen C. Stone
from Xerox PARC and Tony D. deRose from the University of Washington in their joint paper
<a href="http://graphics.pixar.com/people/derose/publications/CubicClassification/paper.pdf">"A Geometric
Characterization of Parametric Cubic curves"</a>. It was published in 1989, and defines curves as having a
"canonical" form (i.e. a form that all curves can be reduced to) from which we can immediately tell which
features a curve will have. So how does it work?</p>
<p>The first observation that makes things work is that if we have a cubic curve with four points, we can apply
a linear transformation to these points such that three of the points end up on (0,0), (0,1) and (1,1), with the
last point then being "somewhere". After applying that transformation, the location of that last point can then
tell us what kind of curve we're dealing with. Specifically, we see the following breakdown:</p>
<Graphic static={true} preset="simple" title="The canonical curve map" setup={this.setup} draw={this.drawBase} />
<p>This is a fairly funky image, so let's see how it breaks down. We see the three fixed points at (0,0),
(0,1) and (1,1), and then the fourth point is somewhere. Depending on where it is, our curve will have
certain features. Namely, if the fourth point is...</p>
<ol>
<li>anywhere on and in the red zone, the curve will be self-intersecting, yielding either a cusp or a loop.
Anywhere inside the the red zone, this will be a loop. We won't know <i>where</i> that loop
is (in terms of <i>t</i> values), but we are guaranteed that there is one.</li>
<li>on the left (red) edge, the curve will have a cusp. We again don't know <em>where</em>, just that it
has one. This edge is described by the function: \[
y = \frac{-x^2 + 2x + 3}{4}, \{ x \leq 1 \}
\]</li>
<li>on the lower right (pink) edge, the curve will have a loop at t=1, so we know the end coordinate of
the curve also lies <em>on</em> the curve. This edge is described by the function: \[
y = \frac{\sqrt{3(4x - x^2)} - x}{2}, \{ 0 \leq x \leq 1 \}
\]</li>
<li>on the top (blue) edge, the curve will have a loop at t=0, so we know the start coordinate of
the curve also lies <em>on</em> the curve. This edge is described by the function: \[
y = \frac{-x^2 + 3x}{3}, \{ x \leq 0 \}
\]</li>
<li>inside the green zone, the curve will have a single inflection, switching concave/convex once.</li>
<li>between the red and green zones, the curve has two inflections, meaning its curvature switches between
concave/convex form twice.</li>
<li>anywhere on the right of the red zone, the curve will have no inflections. It'll just be a well-behaved arch.</li>
</ol>
<p>Of course, this map is fairly small, but the regions extend to infinity, with well defined boundaries.</p>
<div className="note">
<h3>Wait, where do those lines come from?</h3>
<p>Without repeating the paper mentioned at the top of this section, the loop-boundaries come from
rewriting the curve into canonical form, and then solving the formulae for which constraints must
hold for which possible curve properties. In the paper these functions yield formulae for where
you will find cusp points, or loops where we know t=0 or t=1, but those functions are derived
for the full cubic expression, meaning they apply to t=-∞ to t=∞... For Bézier curves we only care
about the "clipped interval" t=0 to t=1, so some of the properties that apply when you look at
the curve over an infinite interval simply don't apply to the Bézier curve interval.</p>
<p>The right bound for the loop region, indicating where the curve switches from "having inflections"
to "having a loop", for the general cubic curve, is actually mirrored over x=1, but for Bézier curves
this right half doesn't apply, so we don't need to pay attention to it. Similarly, the boundaries for
t=0 and t=1 loops are also nice clean curves but get "cut off" when we only look at what the general
curve does over the interval t=0 to t=1.</p>
<p>For the full details, head over to the paper and read through sections 3 and 4. If you still remember
your high school precalculus, you can probably follow along with this paper, although you might have
to read it a few times before all the bits "click".</p>
</div>
<p>So now the question becomes: how do we manipulate our curve so that it fits this canonical form,
with three fixed points, and one "free" point? Enter linear algerba. Don't worry, I'll be doing all
the math for you, as well as show you what the effect is on our curves, but basically we're going
to be using linear algebra, rather than calculus, because "it's way easier". Sometimes a calculus
approach is very hard to work with, when the equivalent geometrical solution is super obvious.</p>
<p>The approach is going to start with a curve that doesn't have all-colinear points (so we
need to make sure the points don't all fall on a straight line), and then applying four graphics
operations that you will probably have heard of: translation (moving all points by some fixed x-
and y-distance), scaling (multiplying all points by some x and y scale factor), and shearing (an
operation that turns rectangles into parallelograms).</p>
<p>Step 1: we translate any curve by -p1.x and -p1.y, so that the curve starts at (0,0). We're going
to make use of an interesting trick here, by pretending our 2D coordinates are 3D, with the <i>z</i>
coordinate simply always being 1. This is an old trick in graphics to overcome the limitations of 2D
transformations: without it, we can only turn (x,y) coordinates into new coordinates of the form
(ax + by, cx + dy), which means we can't do translation, since that requires we end up with some kind
of (x + a, y + b). If we add a bogus <i>z</i> coordinate that is always 1, then we can suddenly add
arbitrary values. For example:</p>
<p>\[
\left [ \begin{array}
01 & 0 & a \\
0 & 1 & b \\
0 & 0 & 1
\end{array} \right ]
\cdot
\left [
\begin{matrix}
x \\
y \\
z=1
\end{matrix}
\right ]
=
\left [
\begin{matrix}
1 \cdot x + 0 \cdot y + a \cdot z \\
0 \cdot x + 1 \cdot y + b \cdot z \\
0 \cdot x + 0 \cdot y + 1 \cdot z
\end{matrix}
\right ]
=
\left [
\begin{matrix}
x + a \cdot 1 \\
y + b \cdot 1 \\
1 \cdot z
\end{matrix}
\right ]
=
\left [
\begin{matrix}
x + a \\
y + b \\
z=1
\end{matrix}
\right ]
\]</p>
<p>Sweet! <i>z</i> stays 1, so we can effectively ignore it entirely, but we added some plain values
to our x and y coordinates. So, if we want to subtract p1.x and p1.y, we use:</p>
<p>\[ T_1 =
\left [ \begin{array}
01 & 0 & -{P_1}_x \\
0 & 1 & -{P_1}_y \\
0 & 0 & 1
\end{array} \right ]
\cdot
\left [
\begin{matrix}
x \\
y \\
1
\end{matrix}
\right ]
=
\left [
\begin{matrix}
1 \cdot x + 0 \cdot y - {P_1}_x \cdot 1 \\
0 \cdot x + 1 \cdot y - {P_1}_y \cdot 1 \\
0 \cdot x + 0 \cdot y + 1 \cdot 1
\end{matrix}
\right ]
=
\left [
\begin{matrix}
x - {P_1}_x \\
y - {P_1}_y \\
1
\end{matrix}
\right ]
\]</p>
<p>Running all our coordinates through this transformation gives a new set of coordinates, let's call those <b>U</b>, where the first coordinate lies on (0,0), and the rest is still somewhat free. Our next job is to make sure point 2 ends up lying on the <i>x=0</i> line, so what we want is a transformation matrix that, when we run it, subtracts <i>x</i> from whatever <i>x</i> we currently have. This is called <a href="https://en.wikipedia.org/wiki/Shear_matrix">shearing</a>, and the typical x-shear matrix and its transformation looks like this:</p>
<p>\[
\left [
\begin{matrix}
1 & S & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{matrix}
\right ]
\cdot
\left [
\begin{matrix}
x \\
y \\
1
\end{matrix}
\right ]
=
\left [
\begin{matrix}
x + S \cdot y \\
y \\
1
\end{matrix}
\right ]
\]</p>
<p>So we want some shearing value that, when multiplied by <i>y</i>, yields <i>-x</i>, so our x coordinate becomes zero. That value is simpy <i>-x/y</i>, because <i>-x/y * y = -x</i>. Done:</p>
<p>\[ T_2 =
\left [
\begin{matrix}
1 & -\frac{ {U_2}_x }{ {U_2}_y } & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{matrix}
\right ]
\]</p>
<p>Now, running this on all our points generates a new set of coordinates, let's call those V, which now have point 1 on (0,0) and point 2 on (0, some-value), and we wanted it at (0,1), so we need to [do some scaling](https://en.wikipedia.org/wiki/Scaling_%28geometry%29) to make sure it ends up at (0,1). Additionally, we want point 3 to end up on (1,1), so we can also scale x to make sure its x-coordinate will be 1 after we run the transform. That means we'll be x-scaling by 1/point3<sub>x</sub>, and y-scaling by point2<sub>y</sub>. This is really easy:</p>
<p>\[ T_3 =
\left [
\begin{matrix}
\frac{1}{ {V_3}_x } & 0 & 0 \\
0 & \frac{1}{ {V_2}_y } & 0 \\
0 & 0 & 1
\end{matrix}
\right ]
\]</p>
<p>Then, finally, this generates a new set of coordinates, let's call those W, of which point 1 lies on (0,0), point 2 lies on (0,1), and point three lies on (1, ...) so all that's left is to make sure point 3 ends up at (1,1) - but we can't scale! Point 2 is already in the right place, and y-scaling would move it out of (0,1) again, so our only option is to y-shear point three, just like how we x-sheared point 2 earlier. In this case, we do the same trick, but with `y/x` rather than `x/y` because we're not x-shearing but y-shearing. Additionally, we don't actually want to end up at zero (which is what we did before) so we need to shear towards an offset, in this case 1:</p>
<p>\[ T_4 =
\left [
\begin{matrix}
1 & 0 & 0 \\
\frac{1 - {W_3}_y}{ {W_3}_x } & 1 & 0 \\
0 & 0 & 1
\end{matrix}
\right ]
\]</p>
<p>And this generates our final set of four coordinates. Of these, we already know that points 1 through 3 are (0,0), (0,1) and (1,1), and only the last coordinate is "free". In fact, given any four starting coordinates, the resulting "transformation mapped" coordinate will be:</p>
<p>\[
mapped_4 = \left (
\begin{matrix}
x = \left (
\frac
{
-x_1 + x_4 - \frac{(-x_1+x_2)(-y_1+y_4)}{-y_1+y_2}
}
{
-x_1+x_3-\frac{(-x_1+x_2)(-y_1+y_3)}{-y_1+y_2}
}
\right )
\\
y = \left (
\frac{(-y_1+y_4)}{-y_1+y_2}
+
\frac
{
\left ( 1 - \frac{-y_1+y_3}{-y_1+y_2} \right )
\left ( -x_1 + x_4 - \frac{(-x_1+x_2)(-y_1+y_4)}{-y_1+y_2} \right )
}
{
-x_1+x_3-\frac{(-x_1+x_2)(-y_1+y_3)}{-y_1+y_2}
}
\right )
\end{matrix}
\right )
\]</p>
<p>That looks very complex, but notice that every coordinate value is being offset by the initial translation, and a lot of terms in there repeat: it's pretty easy to calculate this fast, since there's so much we can cache and reuse while we compute this mapped coordinate!</p>
<p>First, let's just do that translation step as a "preprocessing" operation so we don't have to subtract the values all the time. What does that leave?</p>
<p>\[
... = \left (
\begin{matrix}
x = \left ( x_4 - \frac{x_2 \cdot y_4}{y_2} \middle/ x_3-\frac{x_2 \cdot y_3}{y_2} \right )
\\
y =
\frac{y_4}{y_2}
+
\left ( 1 - \frac{y_3}{y_2} \right )
\cdot
\left ( x_4 - \frac{x_2 \cdot y_4}{y_2} \middle/ x_3-\frac{x_2 \cdot y_3}{y_2} \right )
\end{matrix}
\right )
\]</p>
<p>Suddenly things look a lot simpler: the mapped x is fairly straight forward to compute, and we see that the mapped
y actually contains the mapped x in its entirety, so we'll have that part already available when we need to evaluate
it. In fact, let's pull out all those common factors to see just how simple this is:</p>
<p>\[
... = \left (
\begin{matrix}
x = (x_4 - x_2 \cdot f_{42}) / ( x_3- x_2 \cdot f_{32} )
\\
y =
f_{42}
+
\left ( 1 - f_{32} \right )
\cdot
x
\end{matrix}
\right ), f_{32} = \frac{y_3}{y_2}, f_{42} = \frac{y_4}{y_2}
\]</p>
<p>That's kind of super-simple to write out in code, I think you'll agree. Coding math tends to be easier than the formulae initially make it look!</p>
<div className="note">
<h3>How do you track all that?</h3>
<p>Doing maths can be a pain, so whenever possible, I like to make computers do the work for me. Especially for things like this, I simply use <a href="http://www.wolfram.com/mathematica">Mathematica</a>. Tracking all this math by hand is insane, and we invented computers, literally, to do this for us. I have no reason to use pen and paper when I can write out what I want to do in a program, and have the program do the math for me. And real math, too, with symbols, not with numbers. In fact, <a href="http://pomax.github.io/gh-weblog/downloads/canonical-curve.nb">here's</a> the Mathematica notebook if you want to see how this works for yourself.</p>
<p>Now, I know, you're thinking "but Mathematica is super expensive!" and that's true, it's <a href="http://www.wolfram.com/mathematica-home-edition">$295 for home use</a>, but it's <strong>also</strong> <a href="http://www.wolfram.com/raspberry-pi">free when you buy a $35 raspberry pi</a>. Obviously, I bought a raspberry pi, and I encourage you to do the same. With that, as long as you know what you want to <em>do</em>, Mathematica can just do it for you. And we don't have to be geniusses to work out what the maths looks like. That's what we have computers for.</p>
</div>
<p>So, let's write up a sketch that'll show us the canonical form for any curve drawn in blue, overlaid on our
canonical map, so that we can immediately tell which features our curve must have, based on where the fourth
coordinate is located on the map:</p>
<Graphic preset="simple" title="A cubic curve mapped to canonical form" setup={this.setup} draw={this.draw} />
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -0,0 +1,12 @@
# Component functions
One of the first things people run into when they start using Bézier curves in their own programs is "I know how to draw the curve, but how do I determine the bounding box?". It's actually reasonably straight forward to do so, but it requires having some knowledge on exploiting math to get the values we need. For bounding boxes, we aren't actually interested in the curve itself, but only in its "extremities": the minimum and maximum values the curve has for its x- and y-axis values. If you remember your calculus (provided you ever took calculus, otherwise it's going to be hard to remember) we can determine function extremities using the first derivative of that function, but this poses a problem, since our function is parametric: every axis has its own function.
The solution: compute the derivative for each axis separately, and then fit them back together in the same way we do for the original.
Let's look at how a parametric Bézier curve "splits up" into two normal functions, one for the x-axis and one for the y-axis. Note the left-most figure is again an interactive curve, without labeled axes (you get coordinates in the graph instead). The center and right-most figures are the component functions for computing the x-axis value, given a value for <i>t</i> (between 0 and 1 inclusive), and the y-axis value, respectively.
If you move points in a curve sideways, you should only see the middle graph change; likely, moving points vertically should only show a change in the right graph.
<Graphic preset="simple" title="Quadratic Bézier curve components" setup={this.setupQuadratic} draw={this.draw}/>
<Graphic preset="simple" title="Cubic Bézier curve components" setup={this.setupCubic} draw={this.draw}/>

View File

@@ -1,11 +1,13 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "components";
var Components = React.createClass({
getDefaultProps: function() {
return {
title: "Component functions"
title: locale.getTitle(page)
};
},
@@ -48,36 +50,7 @@ var Components = React.createClass({
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>One of the first things people run into when they start using Bézier curves in their own programs is
"I know how to draw the curve, but how do I determine the bounding box?". It's actually reasonably straight
forward to do so, but it requires having some knowledge on exploiting math to get the values we need.
For bounding boxes, we aren't actually interested in the curve itself, but only in its "extremities": the
minimum and maximum values the curve has for its x- and y-axis values. If you remember your calculus
(provided you ever took calculus, otherwise it's going to be hard to remember) we can determine function
extremities using the first derivative of that function, but this poses a problem, since our function is
parametric: every axis has its own function.</p>
<p>The solution: compute the derivative for each axis separately, and then fit them back together in the same
way we do for the original.</p>
<p>Let's look at how a parametric Bézier curve "splits up" into two normal functions, one for the x-axis and
one for the y-axis. Note the left-most figure is again an interactive curve, without labeled axes (you
get coordinates in the graph instead). The center and right-most figures are the component functions for
computing the x-axis value, given a value for <i>t</i> (between 0 and 1 inclusive), and the y-axis value,
respectively.</p>
<p>If you move points in a curve sideways, you should only see the middle graph change; likely, moving
points vertically should only show a change in the right graph.</p>
<Graphic preset="simple" title="Quadratic Bézier curve components" setup={this.setupQuadratic} draw={this.draw}/>
<Graphic preset="simple" title="Cubic Bézier curve components" setup={this.setupCubic} draw={this.draw}/>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -1,7 +1,7 @@
var React = require("react");
var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var locale = new Locale();
var page = "control";
var Control = React.createClass({

View File

@@ -1,7 +1,7 @@
var React = require("react");
var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var locale = new Locale();
var page = "decasteljau";
var deCasteljau = React.createClass({

View File

@@ -0,0 +1,149 @@
# Derivatives
There's a number of useful things that you can do with Bézier curves based on their derivative, and one of the more amusing observations about Bézier curves is that their derivatives are, in fact, also Bézier curves. In fact, the derivation of a Bézier curve is relatively straight forward, although we do need a bit of math. First, let's look at the derivative rule for Bézier curves, which is:
\[
Bézier'(n,t) = n \cdot \sum_{i=0}^{n-1} (b_{i+1}-b_i) \cdot Bézier(n-1,t)_i
\]
which we can also write (observing that <i>b</i> in this formula is the same as our <i>w</i> weights, and that <i>n</i> times a summation is the same as a summation where each term is multiplied by <i>n</i>) as:
\[
Bézier'(n,t) = \sum_{i=0}^{n-1} Bézier(n-1,t)_i \cdot n \cdot (w_{i+1}-w_i)
\]
Or, in plain text: the derivative of an n<sup>th</sup> degree Bézier curve is an (n-1)<sup>th</sup> degree Bézier curve, with one fewer term, and new weights w'<sub>0</sub>...w'<sub>n-1</sub> derived from the original weights as n(w<sub>i+1</sub> - w<sub>i</sub>), so for a 3rd degree curve, with four weights, the derivative has three new weights w'<sub>0</sub> = 3(w<sub>1</sub>-w<sub>0</sub>), w'<sub>1</sub> = 3(w<sub>2</sub>-w<sub>1</sub>) and w'<sub>2</sub> = 3(w<sub>3</sub>-w<sub>2</sub>).
<div className="note">
### "Slow down, why is that true?"
Sometimes just being told "this is the derivative" is nice, but you might want to see why this is indeed the case. As such, let's have a look at the proof for this derivative. First off, the weights are independent of the full Bézier function, so the derivative involves only the derivative of the polynomial basis function. So, let's find that:
\[
B_{n,k}(t) \frac{d}{dt} = {n \choose k} t^k (1-t)^{n-k} \frac{d}{dt}
\]
Applying the [product](http://en.wikipedia.org/wiki/Product_rule) and [chain](http://en.wikipedia.org/wiki/Chain_rule) rules gives us:
\[\begin{array}{l}
... &= {n \choose k} \left (
k \cdot t^{k-1} (1-t)^{n-k} + t^k \cdot (1-t)^{n-k-1} \cdot (n-k) \cdot -1
\right )
\end{array}\]
Which is hard to work with, so let's expand that properly:
\[\begin{array}{l}
... &= \frac{kn!}{k!(n-k)!} t^{k-1} (1-t)^{n-k} - \frac{(n-k)n!}{k!(n-k)!} t^k (1-t)^{n-1-k}
\end{array}\]
Now, the trick is to turn this expression into something that has binomial coefficients again, so we want to end up with things that look like "x! over y!(x-y)!". If we can do that in a way that involves terms of <i>n-1</i> and <i>k-1</i>, we'll be on the right track.
\[\begin{array}{l}
... &= \frac{n!}{(k-1)!(n-k)!} t^{k-1} (1-t)^{n-k} - \frac{(n-k)n!}{k!(n-k)!} t^k (1-t)^{n-1-k} \\
... &= n \left (
\frac{(n-1)!}{(k-1)!(n-k)!} t^{k-1} (1-t)^{n-k} - \frac{(n-k)(n-1)!}{k!(n-k)!} t^k (1-t)^{n-1-k}
\right ) \\
... &= n \left (
\frac{(n-1)!}{(k-1)!((n-1)-(k-1))!} t^{(k-1)} (1-t)^{(n-1)-(k-1)} - \frac{(n-1)!}{k!((n-1)-k)!} t^k (1-t)^{(n-1)-k}
\right )
\end{array}\]
And that's the first part done: the two components inside the parentheses are actually regular, lower order Bezier expressions:
\[\begin{array}{l}
... &= n \left (
\frac{x!}{y!(x-y)!} t^{y} (1-t)^{x-y} - \frac{x!}{k!(x-k)!} t^k (1-t)^{x-k}
\right )
\ ,\ with\ x=n-1,\ y=k-1
\\
... &= n \left ( B_{(n-1),(k-1)}(t) - B_{(n-1),k}(t) \right )
\end{array}\]
Now to apply this to our weighted Bezier curves. We'll write out the plain curve formula that we saw earlier, and then work our way through to its derivative:
\[\begin{array}{l}
Bézier_{n,k}(t) &=& B_{n,0}(t) \cdot w_0 + B_{n,1}(t) \cdot w_1 + B_{n,2}(t) \cdot w_2 + B_{n,3}(t) \cdot w_3 + ... \\
Bézier_{n,k}(t) \frac{d}{dt} &=& n \cdot (B_{n-1,-1}(t) - B_{n-1,0}(t)) \cdot w_0 + \\
& & n \cdot (B_{n-1,0}(t) - B_{n-1,1}(t)) \cdot w_1 + \\
& & n \cdot (B_{n-1,1}(t) - B_{n-1,2}(t)) \cdot w_2 + \\
& & n \cdot (B_{n-1,2}(t) - B_{n-1,3}(t)) \cdot w_3 + \\
& & ...
\end{array}\]
If we expand this (with some color to show how terms line up), and reorder the terms by increasing values for <i>k</i> we see the following:
\[\begin{array}{l}
n \cdot B_{n-1,-1}(t) \cdot w_0 &+& & \\
n \cdot B_{n-1,BLUE[0]}(t) \cdot w_1 &-& n \cdot B_{n-1,BLUE[0]}(t) \cdot w_0 & + \\
n \cdot B_{n-1,RED[1]}(t) \cdot w_2 &-& n \cdot B_{n-1,RED[1]}(t) \cdot w_1 & + \\
n \cdot B_{n-1,MAGENTA[2]}(t) \cdot w_3 &-& n \cdot B_{n-1,MAGENTA[2]}(t) \cdot w_2 & + \\
... &-& n \cdot B_{n-1,3}(t) \cdot w_3 & + \\
... & & &
\end{array}\]
Two of these terms fall way: the first term falls away because there is no -1<sup>st</sup> term in a summation. As such, it always contributes "nothing", so we can safely completely ignore it for the purpose of finding the derivative function. The other term is the very last term in this expansion: one involving <i>B<sub>n-1,n</sub></i>. This term would have a binomial coefficient of [<i>i</i> choose <i>i+1</i>], which is a non-existent binomial coefficient. Again, this term would contribute "nothing", so we can ignore it, too. This means we're left with:
\[\begin{array}{l}
n \cdot B_{n-1,BLUE[0]}(t) \cdot w_1 &-& n \cdot B_{n-1,BLUE[0]}(t) \cdot w_0 &+ \\
n \cdot B_{n-1,RED[1]}(t) \cdot w_2 &-& \ n \cdot B_{n-1,RED[1]}(t) \cdot w_1 &+ \\
n \cdot B_{n-1,MAGENTA[2]}(t) \cdot w_3 &-& n \cdot B_{n-1,MAGENTA[2]}(t) \cdot w_2 &+ \\
...
\end{array}\]
And that's just a summation of lower order curves:
\[
Bézier_{n,k}(t) \frac{d}{dt} = n \cdot B_{(n-1),BLUE[0]}(t) \cdot (w_1 - w_0)
+ n \cdot B_{(n-1),RED[1]}(t) \cdot (w_2 - w_1)
+ n \cdot B_{(n-1),MAGENTA[2]}(t) \cdot (w_3 - w_2)
\ + \ ...
\]
We can rewrite this as a normal summation, and we're done:
\[
Bézier_{n,k}(t) \frac{d}{dt} = \sum_{k=0}^{n-1} n \cdot B_{n-1,k}(t) \cdot (w_{k+1} - w_k)
= \sum_{k=0}^{n-1} B_{n-1,k}(t) \cdot \underset{derivative\ weights}
{\underbrace{n \cdot (w_{k+1} - w_k)}}
\]
</div>
Let's rewrite that in a form similar to our original formula, so we can see the difference. We will first list our original formula for Bézier curves, and then the derivative:
\[
Bézier(n,t) = \sum_{i=0}^{n}
\underset{binomial\ term}{\underbrace{\binom{n}{i}}}
\cdot\
\underset{polynomial\ term}{\underbrace{(1-t)^{n-i} \cdot t^{i}}}
\cdot\
\underset{weight}{\underbrace{w_i}}
\]
\[
Bézier'(n,t) = \sum_{i=0}^{k}
\underset{binomial\ term}{\underbrace{\binom{k}{i}}}
\cdot\
\underset{polynomial\ term}{\underbrace{(1-t)^{k-i} \cdot t^{i}}}
\cdot\
\underset{derivative\ weight}{\underbrace{n \cdot (w_{i+1} - w_i)}}
{\ , \ with \ k=n-1}
\]
What are the differences? In terms of the actual Bézier curve, virtually nothing! We lowered the order (rather than <i>n</i>, it's now <i>n-1</i>), but it's still the same Bézier function. The only real difference is in how the weights change when we derive the curve's function. If we have four points A, B, C, and D, then the derivative will have three points, the second derivative two, and the third derivative one:
\[ \begin{array}{l}
B(n,t), & & w = \{A,B,C,D\} \\
B'(n,t), & n = 3, & w' = \{A',B',C'\} &= \{3 \cdot (B-A), {\ } 3 \cdot (C-B), {\ } 3 \cdot (D-C)\} \\
B''(n,t), & n = 2, & w'' = \{A'',B''\} &= \{2 \cdot (B'-A'), {\ } 2 \cdot (C'-B')\} \\
B'''(n,t), & n = 1, & w''' = \{A'''\} &= \{1 \cdot (B''-A'')\}
\end{array} \]
We can keep performing this trick for as long as we have more than one weight. Once we have one weight left, the next step will see <i>k = 0</i>, and the result of our "Bézier function" summation is zero, because we're not adding anything at all. As such, a quadratic curve has no second derivative, a cubic curve has no third derivative, and generalized: an <i>n<sup>th</sup></i> order curve has <i>n-1</i> (meaningful) derivatives, with any further derivative being zero.

View File

@@ -1,200 +1,18 @@
var React = require("react");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "derivatives";
var Derivatives = React.createClass({
getDefaultProps: function() {
return {
title: "Derivatives"
title: locale.getTitle(page)
};
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>There's a number of useful things that you can do with Bézier curves based on their derivative,
and one of the more amusing observations about Bézier curves is that their derivatives are, in fact,
also Bézier curves. In fact, the derivation of a Bézier curve is relatively straight forward, although
we do need a bit of math. First, let's look at the derivative rule for Bézier curves, which is:</p>
<p>\[
Bézier'(n,t) = n \cdot \sum_{i=0}^{n-1} (b_{i+1}-b_i) \cdot Bézier(n-1,t)_i
\]</p>
<p>which we can also write (observing that <i>b</i> in this formula is the same as our <i>w</i> weights,
and that <i>n</i> times a summation is the same as a summation where each term is multiplied by <i>n</i>)
as:</p>
<p>\[
Bézier'(n,t) = \sum_{i=0}^{n-1} Bézier(n-1,t)_i \cdot n \cdot (w_{i+1}-w_i)
\]</p>
<p>Or, in plain text: the derivative of an n<sup>th</sup> degree Bézier curve is an (n-1)<sup>th</sup>
degree Bézier curve, with one fewer term, and new weights w'<sub>0</sub>...w'<sub>n-1</sub> derived
from the original weights as n(w<sub>i+1</sub> - w<sub>i</sub>), so for a 3rd degree curve, with four weights,
the derivative has three new weights w'<sub>0</sub> = 3(w<sub>1</sub>-w<sub>0</sub>),
w'<sub>1</sub> = 3(w<sub>2</sub>-w<sub>1</sub>) and w'<sub>2</sub> = 3(w<sub>3</sub>-w<sub>2</sub>).</p>
<div className="note">
<h3>"Slow down, why is that true?"</h3>
<p>Sometimes just being told "this is the derivative" is nice, but you might want to see why
this is indeed the case. As such, let's have a look at the proof for this derivative. First off,
the weights are independent of the full Bézier function, so the derivative involves only the
derivative of the polynomial basis function. So, let's find that:</p>
<p>\[
B_{n,k}(t) \frac{d}{dt} = {n \choose k} t^k (1-t)^{n-k} \frac{d}{dt}
\]</p>
<p>Applying the <a href="http://en.wikipedia.org/wiki/Product_rule">product</a> and
<a href="http://en.wikipedia.org/wiki/Chain_rule">chain</a> rules gives us:</p>
<p>\[\begin{array}{l}
... &= {n \choose k} \left (
k \cdot t^{k-1} (1-t)^{n-k} + t^k \cdot (1-t)^{n-k-1} \cdot (n-k) \cdot -1
\right )
\end{array}\]</p>
<p>Which is hard to work with, so let's expand that properly:</p>
<p>\[\begin{array}{l}
... &= \frac{kn!}{k!(n-k)!} t^{k-1} (1-t)^{n-k} - \frac{(n-k)n!}{k!(n-k)!} t^k (1-t)^{n-1-k}
\end{array}\]</p>
<p>Now, the trick is to turn this expression into something that has binomial
coefficients again, so we want to end up with things that look like "x! over y!(x-y)!".
If we can do that in a way that involves terms of <i>n-1</i> and <i>k-1</i>, we'll
be on the right track.</p>
<p>\[\begin{array}{l}
... &= \frac{n!}{(k-1)!(n-k)!} t^{k-1} (1-t)^{n-k} - \frac{(n-k)n!}{k!(n-k)!} t^k (1-t)^{n-1-k} \\
... &= n \left (
\frac{(n-1)!}{(k-1)!(n-k)!} t^{k-1} (1-t)^{n-k} - \frac{(n-k)(n-1)!}{k!(n-k)!} t^k (1-t)^{n-1-k}
\right ) \\
... &= n \left (
\frac{(n-1)!}{(k-1)!((n-1)-(k-1))!} t^{(k-1)} (1-t)^{(n-1)-(k-1)} - \frac{(n-1)!}{k!((n-1)-k)!} t^k (1-t)^{(n-1)-k}
\right )
\end{array}\]</p>
<p>And that's the first part done: the two components inside the parentheses are actually
regular, lower order Bezier expressions:</p>
<p>\[\begin{array}{l}
... &= n \left (
\frac{x!}{y!(x-y)!} t^{y} (1-t)^{x-y} - \frac{x!}{k!(x-k)!} t^k (1-t)^{x-k}
\right )
\ ,\ with\ x=n-1,\ y=k-1
\\
... &= n \left ( B_{(n-1),(k-1)}(t) - B_{(n-1),k}(t) \right )
\end{array}\]</p>
<p>Now to apply this to our weighted Bezier curves. We'll write out the plain curve formula that
we saw earlier, and then work our way through to its derivative:</p>
<p>\[\begin{array}{l}
Bézier_{n,k}(t) &=& B_{n,0}(t) \cdot w_0 + B_{n,1}(t) \cdot w_1 + B_{n,2}(t) \cdot w_2 + B_{n,3}(t) \cdot w_3 + ... \\
Bézier_{n,k}(t) \frac{d}{dt} &=& n \cdot (B_{n-1,-1}(t) - B_{n-1,0}(t)) \cdot w_0 + \\
& & n \cdot (B_{n-1,0}(t) - B_{n-1,1}(t)) \cdot w_1 + \\
& & n \cdot (B_{n-1,1}(t) - B_{n-1,2}(t)) \cdot w_2 + \\
& & n \cdot (B_{n-1,2}(t) - B_{n-1,3}(t)) \cdot w_3 + \\
& & ...
\end{array}\]</p>
<p>If we expand this (with some color to show how terms line up), and reorder the terms by increasing values for <i>k</i>
we see the following:</p>
<p>\[\begin{array}{l}
n \cdot B_{n-1,-1}(t) \cdot w_0 &+& & \\
n \cdot B_{n-1,BLUE[0]}(t) \cdot w_1 &-& n \cdot B_{n-1,BLUE[0]}(t) \cdot w_0 & + \\
n \cdot B_{n-1,RED[1]}(t) \cdot w_2 &-& n \cdot B_{n-1,RED[1]}(t) \cdot w_1 & + \\
n \cdot B_{n-1,MAGENTA[2]}(t) \cdot w_3 &-& n \cdot B_{n-1,MAGENTA[2]}(t) \cdot w_2 & + \\
... &-& n \cdot B_{n-1,3}(t) \cdot w_3 & + \\
... & & &
\end{array}\]</p>
<p>Two of these terms fall way: the first term falls away because there is no
-1<sup>st</sup> term in a summation. As such, it always contributes "nothing", so
we can safely completely ignore it for the purpose of finding the derivative function.
The other term is the very last term in this expansion: one involving <i>B<sub>n-1,n</sub></i>.
This term would have a binomial coefficient of [<i>i</i> choose <i>i+1</i>], which is
a non-existent binomial coefficient. Again, this term would contribute "nothing", so we
can ignore it, too. This means we're left with:</p>
<p>\[\begin{array}{l}
n \cdot B_{n-1,BLUE[0]}(t) \cdot w_1 &-& n \cdot B_{n-1,BLUE[0]}(t) \cdot w_0 &+ \\
n \cdot B_{n-1,RED[1]}(t) \cdot w_2 &-& \ n \cdot B_{n-1,RED[1]}(t) \cdot w_1 &+ \\
n \cdot B_{n-1,MAGENTA[2]}(t) \cdot w_3 &-& n \cdot B_{n-1,MAGENTA[2]}(t) \cdot w_2 &+ \\
...
\end{array}\]</p>
<p>And that's just a summation of lower order curves:</p>
<p>\[
Bézier_{n,k}(t) \frac{d}{dt} = n \cdot B_{(n-1),BLUE[0]}(t) \cdot (w_1 - w_0)
+ n \cdot B_{(n-1),RED[1]}(t) \cdot (w_2 - w_1)
+ n \cdot B_{(n-1),MAGENTA[2]}(t) \cdot (w_3 - w_2)
\ + \ ...
\]</p>
<p>We can rewrite this as a normal summation, and we're done:</p>
<p>\[
Bézier_{n,k}(t) \frac{d}{dt} = \sum_{k=0}^{n-1} n \cdot B_{n-1,k}(t) \cdot (w_{k+1} - w_k)
= \sum_{k=0}^{n-1} B_{n-1,k}(t) \cdot \underset{derivative\ weights}
{\underbrace{n \cdot (w_{k+1} - w_k)}}
\]</p>
</div>
<p>Let's rewrite that in a form similar to our original formula, so we can see the difference. We will
first list our original formula for Bézier curves, and then the derivative:</p>
<p>\[
Bézier(n,t) = \sum_{i=0}^{n}
\underset{binomial\ term}{\underbrace{\binom{n}{i}}}
\cdot\
\underset{polynomial\ term}{\underbrace{(1-t)^{n-i} \cdot t^{i}}}
\cdot\
\underset{weight}{\underbrace{w_i}}
\]</p>
<p>\[
Bézier'(n,t) = \sum_{i=0}^{k}
\underset{binomial\ term}{\underbrace{\binom{k}{i}}}
\cdot\
\underset{polynomial\ term}{\underbrace{(1-t)^{k-i} \cdot t^{i}}}
\cdot\
\underset{derivative\ weight}{\underbrace{n \cdot (w_{i+1} - w_i)}}
{\ , \ with \ k=n-1}
\]</p>
<p>What are the differences? In terms of the actual Bézier curve, virtually nothing!
We lowered the order (rather than <i>n</i>, it's now <i>n-1</i>), but it's still the
same Bézier function. The only real difference is in how the weights change when we
derive the curve's function. If we have four points A, B, C, and D, then the derivative
will have three points, the second derivative two, and the third derivative one:</p>
<p>\[ \begin{array}{l}
B(n,t), & & w = \{A,B,C,D\} \\
B'(n,t), & n = 3, & w' = \{A',B',C'\} &= \{3 \cdot (B-A), {\ } 3 \cdot (C-B), {\ } 3 \cdot (D-C)\} \\
B''(n,t), & n = 2, & w'' = \{A'',B''\} &= \{2 \cdot (B'-A'), {\ } 2 \cdot (C'-B')\} \\
B'''(n,t), & n = 1, & w''' = \{A'''\} &= \{1 \cdot (B''-A'')\}
\end{array} \]</p>
<p>We can keep performing this trick for as long as we have more than one weight. Once
we have one weight left, the next step will see <i>k = 0</i>, and the result of our
"Bézier function" summation is zero, because we're not adding anything at all. As such,
a quadratic curve has no second derivative, a cubic curve has no third derivative, and
generalized: an <i>n<sup>th</sup></i> order curve has <i>n-1</i> (meaningful) derivatives,
with any further derivative being zero.</p>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -2,7 +2,7 @@ var React = require("react");
var keyHandling = require("../../decorators/keyhandling-decorator.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var locale = new Locale();
var page = "explanation";
var Explanation = React.createClass({

View File

@@ -1,7 +1,7 @@
var React = require("react");
var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var locale = new Locale();
var page = "extended";
var Explanation = React.createClass({

View File

@@ -0,0 +1,173 @@
# Finding extremities: root finding
Now that we understand (well, superficially anyway) the component functions, we can find the extremities of our Bézier curve by finding maxima and minima on the component functions, by solving the equations B'(t) = 0 and B''(t) = 0. Although, in the case of quadratic curves there is no B''(t), so we only need to compute B'(t) = 0. So, how do we compute the first and second derivatives? Fairly easily, actually, until our derivatives are 4th order or higher... then things get really hard. But let's start simple:
### Quadratic curves: linear derivatives.
Finding the solution for "where is this line 0" should be trivial:
\[
\begin{align}
l(x) = ax + b &= 0,\\
ax + b &= 0,\\
ax &= -b \\
x &= \frac{-b}{a}
\end{align}
\]
Done. And quadratic curves have no meaningful second derivative, so we're *really* done.
### Cubic curves: the quadratic formula.
The derivative of a cubic curve is a quadratic curve, and finding the roots for a quadratic Bézier curve means we can apply the [Quadratic formula](https://en.wikipedia.org/wiki/Quadratic_formula). If you've seen it before, you'll remember it, and if you haven't, it looks like this:
\[
Given\ f(t) = at^2 + bt + c,\ f(t)=0\ when\ t = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
\]
So, if we can express a Bézier component function as a plain polynomial, we're done: we just plug in the values into the quadratic formula, check if that square root is negative or not (if it is, there are no roots) and then just compute the two values that come out (because of that plus/minus sign we get two). Any value between 0 and 1 is a root that matters for Bézier curves, anything below or above that is irrelevant (because Bézier curves are only defined over the interval [0,1]). So, how do we convert?
First we turn our cubic Bézier function into a quadratic one, by following the rule mentioned at the end of the [derivatives section](#derivatives):
\[
\begin{align}
B(t)\ uses\ \{ p_1,p_2,p_3,p_4 \} \\
B'(t)\ uses\ \{ v_1.v_2,v_3 \},\ where\ v_1 = 3(p_2-p_1),\ v_2 = 3(p_3-p_2),\ v_3 = 3(p_4-p_3)
\end{align}
\]
And then, using these *v* values, we can find out what our *a*, *b*, and *c* should be:
\[
\begin{align}
B'(t) &= v_1(1-t)^2 + 2v_2(1-t)t + v_3t^2 \\
... &= v_1(t^2 - 2t + 1) + 2v_2(t-t^2) + v_3t^2 \\
... &= v_1t^2 - 2v_1t + v_1 + 2v_2t - 2v_2t^2 + v_3t^2 \\
... &= v_1t^2 - 2v_2t^2 + v_3t^2 - 2v_1t + v_1 + 2v_2t \\
... &= (v_1-2v_2+v_3)t^2 + 2(v_2-v_1)t + v_1
\end{align}
\]
This gives us thee coefficients *a*, *b*, and *c* that are expressed in terms of *v* values, where the *v* values are just convenient expressions of our original *p* values, so we can do some trivial substitution to get:
\[
\begin{align}
a &= v_1-2v_2+v_3 = 3(-p_1 + 3p_2 - 3p_3 + p_4) \\
b &= 2(v_2-v_1) = 6(p_1 - 2p_2 + p_3) \\
c &= v_1 = 3(p_2-p_1)
\end{align}
\]
Easy peasy. We can now almost trivially find the roots by plugging those values into the quadratic formula. We also note that the second derivative of a cubic curve means computing the first derivative of a quadratic curve, and we just saw how to do that in the section above.
### Quartic curves: Cardano's algorithm.
Quartic—fourth degree—curves have a cubic function as derivative. Now, cubic functions are a bit of a problem because they're really hard to solve. But, way back in the 16<sup>th</sup> century, [Gerolamo Cardano](https://en.wikipedia.org/wiki/Gerolamo_Cardano) figured out that even if the general cubic function is really hard to solve, it can be rewritten to a form for which finding the roots is "easy", and then the only hard part is figuring out how to go from that form to the
generic form. So:
\[
very\ hard:\ solve\ at^3 + bt^2 + ct + d = 0\\
easier:\ solve\ t^3 + pt + q = 0
\]
This is easier because for the "easier formula" we can use [regular calculus](http://www.wolframalpha.com/input/?i=t^3+%2B+pt+%2B+q) to find the roots (as a cubic function, however, it can have up to three roots, but two of those can be complex. For the purpose of Bézier curve extremities, we can completely ignore those complex roots, since our *t* is a plain real number from 0 to 1).
So, the trick is to figure out how to turn the first formula into the second formula, and to then work out the maths that gives us the roots. This is explained in detail over at [Ken J. Ward's page](http://www.trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm) for solving the cubic equation, so instead of showing the maths, I'm simply going to show the programming code for solving the cubic equation, with the complex roots getting totally ignored.
<div className="howtocode">
### Implementing Cardano's algorithm for finding all real roots
The "real roots" part is fairly important, because while you cannot take a square, cube, etc. root of a negative number in the "real" number space (denoted with ), this is perfectly fine in the ["complex" number](https://en.wikipedia.org/wiki/Complex_number) space (denoted with ). And, as it so happens, Cardano is also attributed as the first mathematician in history to have made use of complex numbers in his calculations. For this very algorithm!
```
// A helper function to filter for values in the [0,1] interval:
function accept(t) {
return 0<=t && t <=1;
}
// A real-cuberoots-only function:
function crt(v) {
if(v<0) return -Math.pow(-v,1/3);
return Math.pow(v,1/3);
}
// Now then: given cubic coordinates {pa, pb, pc, pd} find all roots.
function getCubicRoots(pa, pb, pc, pd) {
var d = (-pa + 3*pb - 3*pc + pd),
a = (3*pa - 6*pb + 3*pc) / d,
b = (-3*pa + 3*pb) / d,
c = pa / d;
var p = (3*b - a*a)/3,
p3 = p/3,
q = (2*a*a*a - 9*a*b + 27*c)/27,
q2 = q/2,
discriminant = q2*q2 + p3*p3*p3;
// and some variables we're going to use later on:
var u1,v1,root1,root2,root3;
// three possible real roots:
if (discriminant < 0) {
var mp3 = -p/3,
mp33 = mp3*mp3*mp3,
r = sqrt( mp33 ),
t = -q / (2*r),
cosphi = t<-1 ? -1 : t>1 ? 1 : t,
phi = acos(cosphi),
crtr = cuberoot(r),
t1 = 2*crtr;
root1 = t1 * cos(phi/3) - a/3;
root2 = t1 * cos((phi+2*pi)/3) - a/3;
root3 = t1 * cos((phi+4*pi)/3) - a/3;
return [root1, root2, root3].filter(accept);
}
// three real roots, but two of them are equal:
if(discriminant === 0) {
u1 = q2 < 0 ? cuberoot(-q2) : -cuberoot(q2);
root1 = 2*u1 - a/3;
root2 = -u1 - a/3;
return [root1, root2].filter(accept);
}
// one real root, two complex roots
var sd = sqrt(discriminant);
u1 = cuberoot(sd - q2);
v1 = cuberoot(sd + q2);
root1 = u1 - v1 - a/3;
return [root1].filter(accept);
}
```
</div>
And that's it. The maths is complicated, but the code is pretty much just "follow the maths, while caching as many values as we can to reduce recomputing things as much as possible" and now we have a way to find all roots for a cubic function and can just move on with using that to find extremities of our curves.
### Quintic and higher order curves: finding numerical solutions
The problem with this is that as the order of the curve goes up, we can't actually solve those equations the normal way. We can't take the function, and then work out what the solutions are. Not to mention that even solving a third order derivative (for a fourth order curve) is already a royal pain in the backside. We need a better solution. We need numerical approaches.
That's a fancy word for saying "rather than solve the function, treat the problem as a sequence of identical operations, the performing of which gets us closer and closer to the real answer". As it turns out, there is a really nice numerical root finding algorithm, called the [Newton-Raphson](http://en.wikipedia.org/wiki/Newton-Raphson) root finding method (yes, after *[that](https://en.wikipedia.org/wiki/Isaac_Newton)* Newton), which we can make use of.
The Newton-Raphson approach consists of picking a value *t* (any will do), and getting the corresponding value at that *t* value. For normal functions, we can treat that value as a height. If the height is zero, we're done, we have found a root. If it's not, we take the tangent of the curve at that point, and extend it until it passes the x-axis, which will be at some new point *t*. We then repeat the procedure with this new value, and we keep doing this until we find our root.
Mathematically, this means that for some *t*, at step *n=1*, we perform the following calculation until *f<sub>y</sub>*(*t*) is zero, so that the next *t* is the same as the one we already have:
\[
t_{n+1} = t_n - \frac{f_y(t_n)}{f'_y(t_n)}
\]
(The wikipedia article has a decent animation for this process, so I'm not adding a sketch for that here unless there are requests for it)
Now, this works well only if we can pick good starting points, and our curve is continuously differentiable and doesn't have oscillations. Glossing over the exact meaning of those terms, the curves we're dealing with conform to those constraints, so as long as we pick good starting points, this will work. So the question is: which starting points do we pick?
As it turns out, Newton-Raphson is so blindingly fast, so we could get away with just not picking: we simply run the algorithm from *t=0* to *t=1* at small steps (say, 1/200<sup>th</sup>) and the result will be all the roots we want. Of course, this may pose problems for high order Bézier curves: 200 steps for a 200<sup>th</sup> order Bézier curve is going to go wrong, but that's okay: there is no reason, ever, to use Bézier curves of crazy high orders. You might use a fifth order curve to get the "nicest still remotely workable" approximation of a full circle with a single Bézier curve, that's pretty much as high as you'll ever need to go.
### In conclusion:
So now that we know how to do root finding, we can determine the first and second derivative roots for our Bézier curves, and show those roots overlaid on the previous graphics:
<Graphic preset="simple" title="Quadratic Bézier curve extremities" setup={this.setupQuadratic} draw={this.draw}/>
<Graphic preset="simple" title="Cubic Bézier curve extremities" setup={this.setupCubic} draw={this.draw}/>

View File

@@ -1,11 +1,13 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "extremities";
var Extremities = React.createClass({
getDefaultProps: function() {
return {
title: "Finding extremities: root finding"
title: locale.getTitle(page)
};
},
@@ -62,208 +64,7 @@ var Extremities = React.createClass({
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>Now that we understand (well, superficially anyway) the component functions, we can find the extremities of our
Bézier curve by finding maxima and minima on the component functions, by solving the equations B'(t) = 0 and B''(t) = 0.
Although, in the case of quadratic curves there is no B''(t), so we only need to compute B'(t) = 0. So, how do we compute the first and second derivatives? Fairly easily, actually, until our derivatives are 4th order or higher... then things get really hard. But let's start simple:</p>
<h3>Quadratic curves: linear derivatives.</h3>
<p>Finding the solution for "where is this line 0" should be trivial:</p>
<p>\[\begin{align}
l(x) = ax + b &= 0,\\
ax + b &= 0,\\
ax &= -b \\
x &= \frac{-b}{a}
\end{align}\]</p>
<p>Done. And quadratic curves have no meaningful second derivative, so we're <em>really</em> done.</p>
<h3>Cubic curves: the quadratic formula.</h3>
<p>The derivative of a cubic curve is a quadratic curve, and finding the roots for a quadratic Bézier curve means we can apply the <a href="https://en.wikipedia.org/wiki/Quadratic_formula">Quadratic formulat</a>. If you've seen it before, you'll remember it, and if you haven't, it looks like this:</p>
<p>\[
Given\ f(t) = at^2 + bt + c,\ f(t)=0\ when\ t = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
\]</p>
<p>So, if we can express a Bézier component function as a plain polynomial, we're done: we just plug in the values into the quadratic formula, check if that square root is negative or not (if it is, there are no roots) and then just compute the two values that come out (because of that plus/minus sign we get two). Any value between 0 and 1 is a root that matters for Bézier curves, anything below or above that is irrelevant (because Bézier curves are only defined over the interval [0,1]). So, how do we convert?</p>
<p>First we turn our cubic Bézier function into a quadratic one, by following the rule mentioned at the end of the <a href="#derivatives">derivatives section</a>:</p>
<p>\[
B(t)\ uses\ \{ p_1,p_2,p_3,p_4 \} \\
B'(t)\ uses\ \{ v_1.v_2,v_3 \},\ where\ v_1 = 3(p_2-p_1),\ v_2 = 3(p_3-p_2),\ v_3 = 3(p_4-p_3)
\]</p>
<p>And then, using these <em>v</em> values, we can find out what our <em>a</em>, <em>b</em>, and <em>c</em> should be:</p>
<p>\[\begin{align}
B'(t) &= v_1(1-t)^2 + 2v_2(1-t)t + v_3t^2 \\
... &= v_1(t^2 - 2t + 1) + 2v_2(t-t^2) + v_3t^2 \\
... &= v_1t^2 - 2v_1t + v_1 + 2v_2t - 2v_2t^2 + v_3t^2 \\
... &= v_1t^2 - 2v_2t^2 + v_3t^2 - 2v_1t + v_1 + 2v_2t \\
... &= (v_1-2v_2+v_3)t^2 + 2(v_2-v_1)t + v_1
\end{align}\]</p>
<p>So we can find the roots by using:</p>
<p>\[\begin{align}
a &= v_1-2v_2+v_3 = 3(-p_1 + 3p_2 - 3p_3 + p_4) \\
b &= 2(v_2-v_1) = 6(p_1 - 2p_2 + p_3) \\
c &= v_1 = 3(p_2-p_1)
\end{align}\]</p>
<p>Easy peasy. We also note that the second derivative of a cubic curve means computing the first derivative of a quadratic curve, and we just saw how to do that in the section above.</p>
<h3>Quartic curves: Cardano's algorithm.</h3>
<p>Quarticfourth degreecurves have a cubic function as derivative. Now, cubic functions are a bit of a problem because
they're really hard to solve. But, way back in the 16<sup>th</sup> century, <a href="https://en.wikipedia.org/wiki/Gerolamo_Cardano">Gerolamo
Cardano</a> figured out that even if the general cubic function is really hard to solve, it can be rewritten to a form
for which finding the roots is "easy", and then the only hard part is figuring out how to go from that form to the
generic form. So:</p>
<p>\[
very\ hard:\ solve\ at^3 + bt^2 + ct + d = 0\\
easier:\ solve\ t^3 + pt + q = 0
\]</p>
<p>This is easier because for the "easier formula" we can use <a href="http://www.wolframalpha.com/input/?i=t^3+%2B+pt+%2B+q">regular calculus</a> to find the roots (as a cubic function, however, it can have up to three roots, but two of those can be complex. For the purpose of Bézier curve extremities, we can completely ignore those complex roots, since our <em>t</em> is a plain real number from 0 to 1).</p>
<p>So, the trick is to figure out how to turn the first formula into the second formula, and to then work out the maths that gives us
the roots. This is explained in detail over at <a href="http://www.trans4mind.com/personal_development/mathematics/polynomials/cubicAlgebra.htm">Ken
J. Ward's page</a> for solving the cubic equation, so instead of showing the maths, I'm simply going to show the programming code for
solving the cubic equation, with the complex roots getting totally ignored.</p>
<div className="howtocode">
<h3>Implementing Cardano's algorithm for finding all real roots</h3>
<p>The "real roots" part is fairly important, because while you cannot take a square, cube, etc. root of a
negative number in the "real" number space (denoted with ), this is perfectly fine in the <a
href="https://en.wikipedia.org/wiki/Complex_number">"complex" number</a> space (denoted with ).
And, as it so happens, Cardano is also attributed as the first mathematician in history to have
made use of complex numbers in his calculations. For this very algorithm!</p>
<pre>// A helper function to filter for values in the [0,1] interval:
function accept(t) {
return 0<=t && t <=1;
}
// A real-cuberoots-only function:
function crt(v) {
if(v<0) return -Math.pow(-v,1/3);
return Math.pow(v,1/3);
}
// Now then: given cubic coordinates {pa, pb, pc, pd} find all roots.
function getCubicRoots(pa, pb, pc, pd) {
var d = (-pa + 3*pb - 3*pc + pd),
a = (3*pa - 6*pb + 3*pc) / d,
b = (-3*pa + 3*pb) / d,
c = pa / d;
var p = (3*b - a*a)/3,
p3 = p/3,
q = (2*a*a*a - 9*a*b + 27*c)/27,
q2 = q/2,
discriminant = q2*q2 + p3*p3*p3;
// and some variables we're going to use later on:
var u1,v1,root1,root2,root3;
// three possible real roots:
if (discriminant < 0) {
var mp3 = -p/3,
mp33 = mp3*mp3*mp3,
r = sqrt( mp33 ),
t = -q / (2*r),
cosphi = t<-1 ? -1 : t>1 ? 1 : t,
phi = acos(cosphi),
crtr = cuberoot(r),
t1 = 2*crtr;
root1 = t1 * cos(phi/3) - a/3;
root2 = t1 * cos((phi+2*pi)/3) - a/3;
root3 = t1 * cos((phi+4*pi)/3) - a/3;
return [root1, root2, root3].filter(accept);
}
// three real roots, but two of them are equal:
else if(discriminant === 0) {
u1 = q2 < 0 ? cuberoot(-q2) : -cuberoot(q2);
root1 = 2*u1 - a/3;
root2 = -u1 - a/3;
return [root1, root2].filter(accept);
}
// one real root, two complex roots
else {
var sd = sqrt(discriminant);
u1 = cuberoot(sd - q2);
v1 = cuberoot(sd + q2);
root1 = u1 - v1 - a/3;
return [root1].filter(accept);
}
}</pre>
</div>
<p>And that's it. The maths is complicated, but the code is pretty much just "follow the maths, while caching as many values as we can to reduce recomputing things as much as possible" and now we have a way to find all roots for a cubic function and can just move on with using that to find extremities of our curves.</p>
<h3>Quintic and higher order curves: finding numerical solutions</h3>
<p>The problem with this is that as the order of the curve goes up, we can't actually solve those equations the normal
way. We can't take the function, and then work out what the solutions are. Not to mention that even solving a third
order derivative (for a fourth order curve) is already a royal pain in the backside. We need a better solution. We
need numerical approaches.</p>
<p>That's a fancy word for saying "rather than solve the function, treat the problem as a sequence of identical
operations, the performing of which gets us closer and closer to the real answer". As it turns out, there is a
really nice numerical root finding algorithm, called the <a href="http://en.wikipedia.org/wiki/Newton-Raphson">Newton-Raphson</a> root
finding method (yes, after <em><a href="https://en.wikipedia.org/wiki/Isaac_Newton">that</a></em> Newton),
which we can make use of.</p>
<p>The Newton-Raphson approach consists of picking a value <i>t</i> (any will do), and getting the corresponding
value at that <i>t</i> value. For normal functions, we can treat that value as a height. If the height is zero,
we're done, we have found a root. If it's not, we take the tangent of the curve at that point, and extend
it until it passes the x-axis, which will be at some new point <i>t</i>. We then repeat the procedure with this
new value, and we keep doing this until we find our root.</p>
<p>Mathematically, this means that for some <i>t</i>, at step <i>n=1</i>, we perform the following calculation
until <i>f<sub>y</sub></i>(<i>t</i>) is zero, so that the next <i>t</i> is the same as the one we already have:</p>
<p>\[
t_{n+1} = t_n - \frac{f_y(t_n)}{f'_y(t_n)}
\]</p>
<p>(The wikipedia article has a decent animation for this process, so I'm not adding a sketch for that here)</p>
<p>Now, this works well only if we can pick good starting points, and our curve is continuously differentiable
and doesn't have oscillations. Glossing over the exact meaning of those terms, the curves we're dealing with
conform to those constraints, so as long as we pick good starting points, this will work. So the question is:
which starting points do we pick?</p>
<p>As it turns out, Newton-Raphson is so blindingly fast, so we could get away with just not picking:
we simply run the algorithm from <i>t=0</i> to <i>t=1</i> at small steps (say, 1/200<sup>th</sup>) and
the result will be all the roots we want. Of course, this may pose problems for high order Bézier
curves: 200 steps for a 200<sup>th</sup> order Bézier curve is going to go wrong, but that's okay:
there is no reason, ever, to use Bézier curves of crazy high orders. You might use a fifth order curve
to get the "nicest still remotely workable" approximation of a full circle with a single Bézier curve,
that's pretty much as high as you'll ever need to go.</p>
<h3>In conclusion:</h3>
<p>So now that we know how to do root finding, we can determine the first and second derivative roots for our Bézier curves, and show those roots overlaid on the previous graphics:</p>
<Graphic preset="simple" title="Quadratic Bézier curve extremities" setup={this.setupQuadratic} draw={this.draw}/>
<Graphic preset="simple" title="Cubic Bézier curve extremities" setup={this.setupCubic} draw={this.draw}/>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -1,7 +1,7 @@
var React = require("react");
var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var locale = new Locale();
var page = "flattening";
var keyHandling = require("../../decorators/keyhandling-decorator.jsx");

View File

@@ -0,0 +1,96 @@
# Curve inflections
Now that we know how to align a curve, there's one more thing we can calculate: inflection points. Imagine we have a variable size circle that we can slide up against our curve. We place it against the curve and adjust its radius so that where it touches the curve, the curvatures of the curve and the circle are the same, and then we start to slide the circle along the curve - for quadratic curves, we can always do this without the circle behaving oddly: we might have to change the radius of the circle as we slide it along, but it'll always sit against the same side of the curve.
But what happens with cubic curves? Imagine we have an S curve and we place our circle at the start of the curve, and start sliding it along. For a while we can simply adjust the radius and things will be fine, but once we get to the midpoint of that S, something odd happens: the circle "flips" from one side of the curve to the other side, in order for the curvatures to keep matching. This is called an inflection, and we can find out where those happen relatively easily.
What we need to do is solve a simple equation:
\[
C(t) = 0
\]
What we're saying here is that given the curvature function *C(t)*, we want to know for which values of *t* this function is zero, meaning there is no "curvature", which will be exactly at the point between our circle being on one side of the curve, and our circle being on the other side of the curve. So what does *C(t)* look like? Actually something that seems not too hard:
\[
C(t) = Bézier_x\prime(t) \cdot Bézier_y{\prime\prime}(t) - Bézier_y\prime(t) \cdot Bézier_x{\prime\prime}(t)
\]
So the function *C(t)* is wholly defined by the first and second derivative functions for the parametric dimensions of our curve. And as already shown, derivatives of Bézier curves are just simpler Bézier curves, with very easy to compute new coefficients, so this should be pretty easy.
However as we've seen in the section on aligning, aligning lets us simplify things *a lot*, by completely removing the contributions of the first coordinate from most mathematical evaluations, and removing the last *y* coordinate as well by virtue of the last point lying on the x-axis. So, while we can evaluate *C(t) = 0* for our curve, it'll be much easier to first axis-align the curve and *then* evalutating the curvature function.
<div className="note">
### Let's derive the full formula anyway
Of course, before we do our aligned check, let's see what happens if we compute the curvature function without axis-aligning. We start with the first and second derivatives, given our basis functions:
\[
\begin{align*}
& Bézier(t) = x_1(1-t)^3 + 3x_2(1-t)^2t + 3x_3(1-t)t^2 + x_4t^3 \\
& Bézier^\prime(t) = a(1-t)^2 + 2b(1-t)^t + ct^2\ \left\{ a=3(x_2-x_1),b=3(x_3-x_2),c=3(x_4-x_3) \right\} \\
& Bézier^{\prime\prime}(t) = u(1-t) + vt\ \left\{ u=2(b-a),v=2(c-b) \right\}\
\end{align*}
\]
And of course the same functions for *y*:
\[
\begin{align*}
& Bézier(t) = y_1(1-t)^3 + 3y_2(1-t)^2t + 3y_3(1-t)t^2 + y_4t^3 \\
& Bézier^\prime(t) = d(1-t)^2 + 2e(1-t)^t + ft^2\\
& Bézier^{\prime\prime}(t) = w(1-t) + zt
\end{align*}
\]
Asking a computer to now compose the *C(t)* function for us (and to expand it to a readable form of simple terms) gives us this rather overly complicated set of arithmetic expressions:
\[
\begin{array}
-18 t^2 x_2 y_1+36 t^2 x_3 y_1-18 t^2 x_4 y_1+18 t^2 x_1 y_2-54 t^2 x_3 y_2 \\
+36 t^2 x_4 y_2-36 t^2 x_1 y_3+54 t^2 x_2 y_3-18 t^2 x_4 y_3+18 t^2 x_1 y_4 \\
-36 t^2 x_2 y_4+18 t^2 x_3 y_4+36 t x_2 y_1-54 t x_3 y_1+18 t x_4 y_1-36 t x_1 y_2 \\
+54 t x_3 y_2-18 t x_4 y_2+54 t x_1 y_3-54 t x_2 y_3-18 t x_1 y_4+18 t x_2 y_4 \\
-18 x_2 y_1+18 x_3 y_1+18 x_1 y_2-18 x_3 y_2-18 x_1 y_3+18 x_2 y_3
\end{array}
\]
That is... unwieldy. So, we note that there are a lot of terms that involve multiplications involving x1, y1, and y4, which would all disappear if we axis-align our curve, which is why aligning is a great idea.
</div>
Aligning our curve so that three of the eight coefficients become zero, we end up with the following simple term function for *C(t)*:
\[
18 \left ( (3 x_3 y_2+2 x_4 y_2+3 x_2 y_3-x_4 y_3)t^2 + (3 x_3 y_2-x_4 y_2-3 x_2 y_3)t + (x_2 y_3-x_3 y_2) \right )
\]
That's a lot easier to work with: we see a fair number of terms that we can compute and then cache, giving us the following simplification:
\[
\left.\begin{matrix}
a = x_3 \cdot y_2 \\
b = x_4 \cdot y_2 \\
c = x_2 \cdot y_3 \\
d = x_4 \cdot y_3
\end{matrix}\right\}
\ C(t) = 18 \cdot \left ( (-3a + 2b + 3c - d)t^2 + (3a - b - 3c)t + (c - a) \right )
\]
This is a plain quadratic curve, and we know how to solve *C(t) = 0*; we use the quadratic formula:
\[
\left.\begin{matrix}
x =& 18(-3a + 2b + 3c - d) \\
y =& 18(3a - b - 3c) \\
z =& 18(c - a)
\end{matrix}\right\}
\ C(t) = 0 \ \Rightarrow\ t = \frac{-y \pm \sqrt{y^2 - 4 x z}}{2x}
\]
We can easily compute this value *if* the descriminator isn't a negative number (because we only want real roots, not complex roots), and *if* *x* is not zero, because divisions by zero are rather useless.
Taking that into account, we compute *t*, we disregard any *t* value that isn't in the Bézier interval [0,1], and we now know at which *t* value(s) our curve will inflect.
<Graphic title="Finding cubic Bézier curve inflections" setup={this.setupCubic} draw={this.draw}/>

View File

@@ -1,16 +1,18 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "inflections";
var ABC = React.createClass({
getDefaultProps: function() {
return {
title: "Curve inflections"
title: locale.getTitle(page)
};
},
setupCubic: function(api) {
var curve = api.getDefaultCubic();
var curve = new api.Bezier(135,25, 25, 135, 215,75, 215,240);
api.setCurve(curve);
},
@@ -19,134 +21,14 @@ var ABC = React.createClass({
api.drawSkeleton(curve);
api.drawCurve(curve);
api.setColor("red");
curve.inflections().forEach(function(t) {
api.drawCircle(curve.get(t), 5);
});
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>Now that we know how to align a curve, there's one more thing we can calculate: inflection points.
Imagine we have a variable size circle that we can slide up against our curve. We place it against
the curve and adjust its radius so that where it touches the curve, the curvatures of the curve and
the circle are the same, and then we start to slide the circle along the curve - for quadratic
curves, we can always do this without the circle behaviour oddly: we might have to change the
radius of the circle as we slide it alone, but it'll always sit against the same side of the curve.</p>
<p>But what happens with cubic curves? Imagine we have an S curve and we place our circle at the
start of the curve, and start sliding it along. For a while we can simply adjust the radius and
things will be fine, but once we get to the midpoint of that S, something odd happens: the circle
"flips" from one side of the curve to the other side, in order for the curvatures to keep matching.
This is called an inflection, and we can find out where those happen relatively easily.</p>
<p>What we need to do is solve a simple equation:</p>
<p>\[
C(t) = 0
\]</p>
<p>What we're saying here is that given the curvature function <i>C(t)</i>, we want to know for which
values of <i>t</i> this function is zero, meaning there is no "curvature", which will be exactly
at the point between our circle being on one side of the curve, and our circle being on the other
side of the curve. So what does <i>C(t)</i> look like? Actually something that seems not too hard:</p>
<p>\[
C(t) = Bézier_x\prime(t) \cdot Bézier_y{\prime\prime}(t) - Bézier_y\prime(t) \cdot Bézier_x{\prime\prime}(t)
\]</p>
<p>So the function <i>C(t)</i> is wholly defined by the first and second derivative functions for the
parametric dimensions of our curve. And as already shown, derivatives of Bézier curves are just
simpler Bézier curves, with very easy to compute new coefficients, so this should be pretty easy.</p>
<p>However as we've seen in the section on aligning, aligning lets us simplify things <em>a lot</em>,
by completely removing the contributions of the first coordinate from most mathematical evaluations,
and removing the last <i>y</i> coordinate as well by virtue of the last point lying on the x-axis. So,
while we can evaluate <i>C(t) = 0</i> for our curve, it'll be much easier to first axis-align the curve
and <em>then</em> evalutating the curvature function.</p>
<div className="note">
<h3>Let's derive the full formula anyway</h3>
<p>Of course, before we do our aligned check, let's see what happens if we compute the curvature
function without axis-aligning. We start with the first and second derivatives, given our basis
functions:</p>
<p>\[\begin{align*}
& Bézier(t) = x_1(1-t)^3 + 3x_2(1-t)^2t + 3x_3(1-t)t^2 + x_4t^3 \\
& Bézier^\prime(t) = a(1-t)^2 + 2b(1-t)^t + ct^2\ \left\{ a=3(x_2-x_1),b=3(x_3-x_2),c=3(x_4-x_3) \right\} \\
& Bézier^{\prime\prime}(t) = u(1-t) + vt\ \left\{ u=2(b-a),v=2(c-b) \right\}\
\end{align*}\]</p>
<p>And of course the same functions for <i>y</i>:</p>
<p>\[\begin{align*}
& Bézier(t) = y_1(1-t)^3 + 3y_2(1-t)^2t + 3y_3(1-t)t^2 + y_4t^3 \\
& Bézier^\prime(t) = d(1-t)^2 + 2e(1-t)^t + ft^2\\
& Bézier^{\prime\prime}(t) = w(1-t) + zt
\end{align*}\]</p>
<p>Asking a computer to now compose the <i>C(t)</i> function for us (and to expand it to
a readable form of simple terms) gives us this rather overly complicated set of arithmetic
expressions:</p>
<p>\[\begin{array}
-18 t^2 x_2 y_1+36 t^2 x_3 y_1-18 t^2 x_4 y_1+18 t^2 x_1 y_2-54 t^2 x_3 y_2 \\
+36 t^2 x_4 y_2-36 t^2 x_1 y_3+54 t^2 x_2 y_3-18 t^2 x_4 y_3+18 t^2 x_1 y_4 \\
-36 t^2 x_2 y_4+18 t^2 x_3 y_4+36 t x_2 y_1-54 t x_3 y_1+18 t x_4 y_1-36 t x_1 y_2 \\
+54 t x_3 y_2-18 t x_4 y_2+54 t x_1 y_3-54 t x_2 y_3-18 t x_1 y_4+18 t x_2 y_4 \\
-18 x_2 y_1+18 x_3 y_1+18 x_1 y_2-18 x_3 y_2-18 x_1 y_3+18 x_2 y_3
\end{array}\]</p>
<p>That is... unwieldy. So, we note that there are a lot of terms that involve multiplications
involving x1, y1, and y4, which would all disappear if we axis-align our curve, which is why
aligning is a great idea.</p>
</div>
<p>Aligning our curve so that three of the eight coefficients become zero, we end up with the
following simple term function for <i>C(t)</i>:</p>
<p>\[
18 \left ( (3 x_3 y_2+2 x_4 y_2+3 x_2 y_3-x_4 y_3)t^2 + (3 x_3 y_2-x_4 y_2-3 x_2 y_3)t + (x_2 y_3-x_3 y_2) \right )
\]</p>
<p>That's a lot easier to work with: we see a fair number of terms that we can compute and then cache,
giving us the following simplification:</p>
<p>\[
\left.\begin{matrix}
a = x_3 \cdot y_2 \\
b = x_4 \cdot y_2 \\
c = x_2 \cdot y_3 \\
d = x_4 \cdot y_3
\end{matrix}\right\}
\ C(t) = 18 \cdot (-3a + 2b + 3c - d)t^2 + (3a - b - 3c)t + (c - a)
\]</p>
<p>This is a plain quadratic curve, and we know how to solve <i>C(t) = 0</i>; we use the quadratic
formula:</p>
<p>\[
\left.\begin{matrix}
x =& 18(-3a + 2b + 3c - d) \\
y =& 18(3a - b - 3c) \\
z =& 18(c - a)
\end{matrix}\right\}
\ C(t) = 0 \ \Rightarrow\ t = \frac{-y \pm \sqrt{y^2 - 4 x z}}{2x}
\]</p>
<p>We can easily compute this value <em>if</em> the descriminator isn't a negative number (because
we only want real roots, not complex roots), and <em>if</em> v<sub>1</sub> is not zero, because
divisions by zero are rather useless.</p>
<p>Taking that into account, we compute <i>t</i>, we disregard any <i>t</i> value that isn't in
the Bézier interval [0,1], and we now know at which <i>t</i> value(s) our curve will inflect.</p>
<Graphic title="Finding cubic Bézier curve inflections" setup={this.setupCubic} draw={this.draw}/>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -1,7 +1,7 @@
var React = require("react");
var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var locale = new Locale();
var page = "introduction";
var Introduction = React.createClass({

View File

@@ -1,7 +1,7 @@
var React = require("react");
var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var locale = new Locale();
var page = "matrix";
var Matrix = React.createClass({

View File

@@ -1,7 +1,7 @@
var React = require("react");
var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var locale = new Locale();
var page = "matrixsplit";
var MatrixSplit = React.createClass({

View File

@@ -0,0 +1,78 @@
# Tangents and normals
If you want to move objects along a curve, or "away from" a curve, the two vectors you're most interested in are the tangent vector and normal vector for curve points. These are actually really easy to find. For moving, and orienting, along a curve we use the tangent, which indicates the direction travel at specific points, and is literally just the first derivative of our curve:
\[
\left \{ \begin{matrix}
tangent_x(t) = B'_x(t) \\
tangent_y(t) = B'_y(t)
\end{matrix} \right.
\]
This gives us the directional vector we want. We can normalize it to give us uniform directional vectors (having a length of 1.0) at each point, and then do whatever it is we want to do based on those directions:
\[
d = || tangent(t) || = \sqrt{B'_x(t)^2 + B'_y(t)^2}
\]
\[
\left \{ \begin{matrix}
\hat{x}(t) = || tangent_x(t) ||
=\frac{tangent_x(t)}{ || tangent(t) || }
= \frac{B'_x(t)}{d} \\
\hat{y}(t) = || tangent_y(t) ||
= \frac{tangent_y(t)}{ || tangent(t) || }
= \frac{B'_y(t)}{d}
\end{matrix} \right.
\]
The tangent is very useful for moving along a line, but what if we want to move away from the curve instead, perpendicular to the curve at some point <i>t</i>? In that case we want the "normal" vector. This vector runs at a right angle to the direction of the curve, and is typically of length 1.0, so all we have to do is rotate the normalized directional vector and we're done:
\[
\left \{ \begin{array}{l}
normal_x(t) = \hat{x}(t) \cdot \cos{\frac{\pi}{2}} - \hat{y}(t) \cdot \sin{\frac{\pi}{2}} = - \hat{y}(t) \\
normal_y(t) = \underset{quarter\ circle\ rotation} {\underbrace{ \hat{x}(t) \cdot \sin{\frac{\pi}{2}} + \hat{y}(t) \cdot \cos{\frac{\pi}{2}} }} = \hat{x}(t)
\end{array} \right.
\]
<div className="note">
Rotating coordinates is actually very easy, if you know the rule for it. You might find it explained as "applying a [rotation matrix](https://en.wikipedia.org/wiki/Rotation_matrix), which is what we'll look at here, too. Essentially, the idea is to take the circles over which we can rotate, and simply "sliding the coordinates" over these circles by the desired
angle. If we want a quarter circle turn, we take the coordinate, slide it along the cirle by a quarter turn, and done.
To turn any point <i>(x,y)</i> into a rotated point <i>(x',y')</i> (over 0,0) by some angle φ, we apply this nicely easy computation:
\[\begin{array}{l}
x' = x \cdot \cos(\phi) - y \cdot \sin(\phi) \\
y' = x \cdot \sin(\phi) + y \cdot \cos(\phi)
\end{array}\]
Which is the "long" version of the following matrix transformation:
\[
\begin{bmatrix}
x' \\ y'
\end{bmatrix}
=
\begin{bmatrix}
\cos(\phi) & -\sin(\phi) \\
\sin(\phi) & \cos(\phi)
\end{bmatrix}
\begin{bmatrix}
x \\ y
\end{bmatrix}
\]
And that's all we need to rotate any coordinate. Note that for quarter, half and three quarter turns these functions become even easier, since *sin* and *cos* for these angles are, respectively: 0 and 1, -1 and 0, and 0 and -1.
But ***why*** does this work? Why this matrix multiplication? [Wikipedia](http://en.wikipedia.org/wiki/Rotation_matrix#Decomposition_into_shears) (Technically, Thomas Herter and Klaus Lott) tells us that a rotation matrix can be
treated as a sequence of three (elementary) shear operations. When we combine this into a single matrix operation (because all matrix multiplications can be collapsed), we get the matrix that you see above. [DataGenetics](http://datagenetics.com/blog/august32013/index.html) have an excellent article about this very thing: it's really quite cool, and I strongly recommend taking a quick break from this primer to read that article.
</div>
The following two graphics show the tangent and normal along a quadratic and cubic curve, with the direction vector coloured blue, and the normal vector coloured red (the markers are spaced out evenly as *t*-intervals, not spaced equidistant).
<div className="figure">
<Graphic preset="simple" title="Quadratic Bézier tangents and normals" inline={true} setup={this.setupQuadratic} draw={this.draw}/>
<Graphic preset="simple" title="Cubic Bézier tangents and normals" inline={true} setup={this.setupCubic} draw={this.draw}/>
</div>

View File

@@ -1,11 +1,13 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "pointvectors";
var PointVectors = React.createClass({
getDefaultProps: function() {
return {
title: "Tangents and normals"
title: locale.getTitle(page)
};
},
@@ -42,107 +44,7 @@ var PointVectors = React.createClass({
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>If you want to move objects along a curve, or "away from" a curve, the two vectors you're most interested
in are the tangent vector and normal vector for curve points. These are actually really easy to find. For
moving, and orienting, along a curve we use the tangent, which indicates the direction travel at specific
points, and is literally just the first derivative of our curve:</p>
<p>\[
\left \{ \begin{matrix}
tangent_x(t) = B'_x(t) \\
tangent_y(t) = B'_y(t)
\end{matrix} \right. \]</p>
<p>This gives us the directional vector we want. We can normalize it to give us uniform directional vectors
(having a length of 1.0) at each point, and then do whatever it is we want to do based on those directions:</p>
<p>\[
d = || tangent(t) || = \sqrt{B'_x(t)^2 + B'_y(t)^2}
\]</p>
<p>\[
\left \{ \begin{matrix}
\hat{x}(t) = || tangent_x(t) ||
=\frac{tangent_x(t)}{ || tangent(t) || }
= \frac{B'_x(t)}{d} \\
\hat{y}(t) = || tangent_y(t) ||
= \frac{tangent_y(t)}{ || tangent(t) || }
= \frac{B'_y(t)}{d}
\end{matrix} \right. \]</p>
<p>The tangent is very useful for moving along a line, but what if we want to move away from the curve instead,
perpendicular to the curve at some point <i>t</i>? In that case we want the "normal" vector. This vector runs
at a right angle to the direction of the curve, and is typically of length 1.0, so all we have to do is rotate
the normalized directional vector and we're done:</p>
<p>\[
\left \{ \begin{array}{l}
normal_x(t) = \hat{x}(t) \cdot \cos{\frac{\pi}{2}} - \hat{y}(t) \cdot \sin{\frac{\pi}{2}} = - \hat{y}(t) \\
normal_y(t) = \underset{quarter\ circle\ rotation} {\underbrace{ \hat{x}(t) \cdot \sin{\frac{\pi}{2}} + \hat{y}(t) \cdot \cos{\frac{\pi}{2}} }} = \hat{x}(t)
\end{array} \right. \]</p>
<div className="note">
<p>Rotating coordinates is actually very easy, if you know the rule for it. You might find
it explained as "applying a <a href="https://en.wikipedia.org/wiki/Rotation_matrix">rotation matrix</a>",
which is what we'll look at here, too. Essentially, the idea is to take the circles over
which we can rotate, and simply "sliding the coordinates" over those circles by the desired
angle. If we want a quarter circle turn, we take the coordinate, slide it along the cirle
by a quarter turn, and done.</p>
<p>To turn any point <i>(x,y)</i> into a rotated point <i>(x',y')</i> (over 0,0) by
some angle φ, we apply this nicely easy computation:</p>
<p>\[\begin{array}{l}
x' = x \cdot \cos(\phi) - y \cdot \sin(\phi) \\
y' = x \cdot \sin(\phi) + y \cdot \cos(\phi)
\end{array}\]</p>
<p>Which is the "long" version of the following matrix transformation:</p>
<p>\[
\begin{bmatrix}
x' \\ y'
\end{bmatrix}
=
\begin{bmatrix}
\cos(\phi) & -\sin(\phi) \\
\sin(\phi) & \cos(\phi)
\end{bmatrix}
\begin{bmatrix}
x \\ y
\end{bmatrix}
\]</p>
<p>And that's all we need to rotate any coordinate. Note that for quarter, half
and three quarter turns these functions become even easier, since <i>sin</i> and
<i>cos</i> for these angles are, respectively: 0 and 1, -1 and 0, and 0 and -1.</p>
<p>But <strong><em>why</em></strong> does this work? Why this matrix multiplication?
<a href="http://en.wikipedia.org/wiki/Rotation_matrix#Decomposition_into_shears">wikipedia</a>
(Technically, Thomas Herter and Klaus Lott) tells us that a rotation matrix can be
treated as a sequence of three (elementary) shear operations. When we combine this into
a single matrix operation (because all matrix multiplications can be collapsed), we get
the matrix that you see above.
<a href="http://datagenetics.com/blog/august32013/index.html">DataGenetics</a> have an
excellent article about this very thing: it's really quite cool, and I strongly recommend
taking a quick break from this primer to read that article.</p>
</div>
<p>The following two graphics show the tangent and normal along a quadratic and cubic curve, with
the direction vector coloured blue, and the normal vector coloured red (the markers are spaced out
evenly as <i>t</i>-intervals, not spaced equidistant).</p>
<div className="figure">
<Graphic preset="simple" title="Quadratic Bézier tangents and normals" inline={true} setup={this.setupQuadratic} draw={this.draw}/>
<Graphic preset="simple" title="Cubic Bézier tangents and normals" inline={true} setup={this.setupCubic} draw={this.draw}/>
</div>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -1,7 +1,7 @@
var React = require("react");
var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var locale = new Locale();
var page = "preface";
var Preface = React.createClass({

View File

@@ -0,0 +1,25 @@
# Lowering and elevating curve order
One interesting property of Bézier curves is that an *n<sup>th</sup>* order curve can always be perfectly represented by an *(n+1)<sup>th</sup>* order curve, by giving the higher order curve specific control points.
If we have a curve with three points, then we can create a four point curve that exactly reproduce the original curve as long as we give it the same start and end points, and for its two control points we pick "1/3<sup>rd</sup> start + 2/3<sup>rd</sup> control" and "2/3<sup>rd</sup> control + 1/3<sup>rd</sup> end", and now we have exactly the same curve as before, except represented as a cubic curve, rather than a quadratic curve.
The general rule for raising an *n<sup>th</sup>* order curve to an *(n+1)<sup>th</sup>* order curve is as follows (observing that the start and end weights are the same as the start and end weights for the old curve):
\[
Bézier(k,t) = \sum_{i=0}^{k}
\underset{binomial\ term}{\underbrace{\binom{k}{i}}}
\cdot\
\underset{polynomial\ term}{\underbrace{(1-t)^{k-i} \cdot t^{i}}}
\ \cdot \
\underset{new\ weights}{\underbrace{\left ( \frac{(k-i) \cdot w_i + i \cdot w_{i-1}}{k} \right )}}
\ ,\ with\ k = n+1\ and\ w_{i-1}=0\ when\ i = 0
\]
However, this rule also has as direct consequence that you **cannot** generally safely lower a curve from *n<sup>th</sup>* order to *(n-1)<sup>th</sup>* order, because the control points cannot be "pulled apart" cleanly. We can try to, but the resulting curve will not be identical to the original, and may in fact look completely different.
We can apply this to a (semi) random curve, as is done in the following graphic. Select the sketch and press your up and down arrow keys to elevate or lower the curve order.
<Graphic preset="simple" title={"A " + this.getOrder() + " order Bézier curve"} setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown} />
There is a good, if mathematical, explanation on the matrices necessary for optimal reduction over on [Sirver's Castle](http://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves), which given time will find its way in a more direct description into this article.

View File

@@ -1,9 +1,17 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var keyHandling = require("../../decorators/keyhandling-decorator.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "reordering";
var Reordering = React.createClass({
getDefaultProps: function() {
return {
title: locale.getTitle(page)
};
},
statics: {
// Improve this based on http://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves/
lower: function(curve) {
@@ -34,12 +42,6 @@ var Reordering = React.createClass({
}
},
getDefaultProps: function() {
return {
title: "Lowering and elevating curve order"
};
},
getInitialState: function() {
return {
order: 0
@@ -116,48 +118,7 @@ var Reordering = React.createClass({
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>One interesting property of Bézier curves is that an <i>n<sup>th</sup></i> order curve can
always be perfectly represented by an <i>(n+1)<sup>th</sup></i> order curve, by giving the
higher order curve specific control points.</p>
<p>If we have a curve with three points, then we can create a four point curve that exactly
reproduce the original curve as long as we give it the same start and end points, and for
its two control points we pick "1/3<sup>rd</sup> start + 2/3<sup>rd</sup> control" and
"2/3<sup>rd</sup> control + 1/3<sup>rd</sup> end", and now we have exactly the same curve as
before, except represented as a cubic curve, rather than a quadratic curve.</p>
<p>The general rule for raising an <i>n<sup>th</sup></i> order curve to an <i>(n+1)<sup>th</sup></i>
order curve is as follows (observing that the start and end weights are the same as the start and
end weights for the old curve):</p>
<p>\[
Bézier(k,t) = \sum_{i=0}^{k}
\underset{binomial\ term}{\underbrace{\binom{k}{i}}}
\cdot\
\underset{polynomial\ term}{\underbrace{(1-t)^{k-i} \cdot t^{i}}}
\ \cdot \
\underset{new\ weights}{\underbrace{\left ( \frac{(k-i) \cdot w_i + i \cdot w_{i-1}}{k} \right )}}
\ ,\ with\ k = n+1\ and\ w_{i-1}=0\ when\ i = 0
\]</p>
<p>However, this rule also has as direct consequence that you <strong>cannot</strong> generally
safely lower a curve from <i>n<sup>th</sup></i> order to <i>(n-1)<sup>th</sup></i> order, because
the control points cannot be "pulled apart" cleanly. We can try to, but the resulting curve will
not be identical to the original, and may in fact look completely different.</p>
<p>We can apply this to a (semi) random curve, as is done in the following graphic. Select the sketch
and press your up and down arrow keys to elevate or lower the curve order.</p>
<Graphic preset="simple" title={"A " + this.getOrder() + " order Bézier curve"} setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown} />
<p>There is a good, if mathematical, explanation on the matrices necessary for optimal reduction
over on <a href="http://www.sirver.net/blog/2011/08/23/degree-reduction-of-bezier-curves/">Sirver's Castle</a>,
which given time will find its way in a more direct description into this article.</p>
</section>
<section>{ locale.getContent(page, this) }</section>
);
}
});

View File

@@ -1,7 +1,7 @@
var React = require("react");
var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var locale = new Locale();
var page = "splitting";
var Splitting = React.createClass({

View File

@@ -0,0 +1,8 @@
# Tight boxes
With our knowledge of bounding boxes, and curve alignment, We can now form the "tight" bounding box for curves. We first align our curve, recording the translation we performed, "T", and the rotation angle we used, "R". We then determine the aligned curve's normal bounding box. Once we have that, we can map that bounding box back to our original curve by rotating it by -R, and then translating it by -T. We now have nice tight bounding boxes for our curves:
<Graphic preset="twopanel" title="Aligning a quadratic curve" setup={this.setupQuadratic} draw={this.draw} />
<Graphic preset="twopanel" title="Aligning a cubic curve" setup={this.setupCubic} draw={this.draw} />
These are, strictly speaking, not necessarily the tightest possible bounding boxes. It is possible to compute the optimal bounding box by determining which spanning lines we need to effect a minimal box area, but because of the parametric nature of Bézier curves this is actually a rather costly operation, and the gain in bounding precision is often not worth it. If there is high demand for it, I'll add a section on how to precisely compute the best fit bounding box, but the maths is fairly gruelling and just not really worth spending time on.

View File

@@ -1,11 +1,13 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "tightbounds";
var TightBounds = React.createClass({
getDefaultProps: function() {
return {
title: "Tight boxes"
title: locale.getTitle(page)
};
},
@@ -80,27 +82,7 @@ var TightBounds = React.createClass({
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>With our knowledge of bounding boxes, and curve alignment, We can now form the "tight" bounding box for
curves. We first align our curve, recording the translation we performed, "T", and the rotation angle we
used, "R". We then determine the aligned curve's normal bounding box. Once we have that, we can map that
bounding box back to our original curve by rotating it by -R, and then translating it by -T. We now have
nice tight bounding boxes for our curves:</p>
<Graphic preset="twopanel" title="Aligning a quadratic curve" setup={this.setupQuadratic} draw={this.draw} />
<Graphic preset="twopanel" title="Aligning a cubic curve" setup={this.setupCubic} draw={this.draw} />
<p>These are, strictly speaking, not necessarily the tightest possible bounding boxes. It is possible to compute
the optimal bounding box by determining which spanning lines we need to effect a minimal box area, but because
of the parametric nature of Bézier curves this is actually a rather costly operation, and the gain in bounding
precision is often not worth it. If there is high demand for it, I'll add a section on how to precisely compute
the best fit bounding box, but the maths is fairly gruelling and just not really worth spending time on.</p>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -1,7 +1,7 @@
var React = require("react");
var Locale = require("../../../lib/locale");
var locale = new Locale("en-GB");
var locale = new Locale();
var page = "whatis";
var Whatis = React.createClass({