1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-29 11:10:38 +02:00

sections 5 and 6

This commit is contained in:
Pomax
2015-12-30 16:47:44 -08:00
parent 697330ed54
commit 83ba8e1bf4
16 changed files with 2081 additions and 1134 deletions

View File

@@ -33,7 +33,6 @@ var Graphic = React.createClass({
onMouseDown={this.mouseDown}
onMouseMove={this.mouseMove}
onMouseUp={this.mouseUp}
onMouseOver={this.mouseOver}
onClick={this.mouseClick}
/>
<figcaption>{this.props.title}</figcaption>
@@ -217,6 +216,23 @@ var Graphic = React.createClass({
this.drawCoordinates(curve, offset);
},
drawHull: function(curve, t, offset) {
var hull = curve.hull(t);
if(hull.length === 6) {
this.drawLine(hull[0], hull[1], offset);
this.drawLine(hull[1], hull[2], offset);
this.drawLine(hull[3], hull[4], offset);
} else {
this.drawLine(hull[0], hull[1], offset);
this.drawLine(hull[1], hull[2], offset);
this.drawLine(hull[2], hull[3], offset);
this.drawLine(hull[4], hull[5], offset);
this.drawLine(hull[5], hull[6], offset);
this.drawLine(hull[7], hull[8], offset);
}
return hull;
},
drawCoordinates: function(curve, offset) {
offset = offset || { x:0, y:0 };
var pts = curve.points;

View File

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

View File

@@ -7,49 +7,49 @@ module.exports = {
introduction: require("./introduction"),
whatis: require("./whatis"),
explanation: require("./explanation"),
control: require("./control")
control: require("./control"),
matrix: require("./matrix"),
decasteljau: require("./decasteljau")
};
/*
matrix: require("./matrix.jsx"),
decasteljau: require("./decasteljau.jsx"),
flattening: require("./flattening.jsx"),
splitting: require("./splitting.jsx"),
matrixsplit: require("./matrixsplit.jsx"),
reordering: require("./reordering.jsx"),
flattening: require("./flattening"),
splitting: require("./splitting"),
matrixsplit: require("./matrixsplit"),
reordering: require("./reordering"),
derivatives: require("./derivatives.jsx"),
pointvectors: require("./pointvectors.jsx"),
components: require("./components.jsx"),
extremities: require("./extremities.jsx"),
boundingbox: require("./boundingbox.jsx"),
aligning: require("./aligning.jsx"),
tightbounds: require("./tightbounds.jsx"),
canonical: require("./canonical.jsx"),
derivatives: require("./derivatives"),
pointvectors: require("./pointvectors"),
components: require("./components"),
extremities: require("./extremities"),
boundingbox: require("./boundingbox"),
aligning: require("./aligning"),
tightbounds: require("./tightbounds"),
canonical: require("./canonical"),
arclength: require("./arclength.jsx"),
arclengthapprox: require("./arclengthapprox.jsx"),
tracing: require("./tracing.jsx"),
arclength: require("./arclength"),
arclengthapprox: require("./arclengthapprox"),
tracing: require("./tracing"),
intersections: require("./intersections.jsx"),
curveintersection: require("./curveintersection.jsx"),
moulding: require("./moulding.jsx"),
pointcurves: require("./pointcurves.jsx"),
intersections: require("./intersections"),
curveintersection: require("./curveintersection"),
moulding: require("./moulding"),
pointcurves: require("./pointcurves"),
catmullconv: require("./catmullconv.jsx"),
catmullmoulding: require("./catmullmoulding.jsx"),
catmullconv: require("./catmullconv"),
catmullmoulding: require("./catmullmoulding"),
polybezier: require("./polybezier.jsx"),
shapes: require("./shapes.jsx"),
polybezier: require("./polybezier"),
shapes: require("./shapes"),
projections: require("./projections.jsx"),
projections: require("./projections"),
offsetting: require("./offsetting.jsx"),
graduatedoffset: require("./graduatedoffset.jsx"),
offsetting: require("./offsetting"),
graduatedoffset: require("./graduatedoffset"),
circles: require("./circles.jsx"),
circles_cubic: require("./circles_cubic.jsx"),
arcapproximation: require("./arcapproximation.jsx")
circles: require("./circles"),
circles_cubic: require("./circles_cubic"),
arcapproximation: require("./arcapproximation")
*/

View File

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

View File

@@ -60,7 +60,7 @@ var Whatis = React.createClass({
<p>While it doesn't look like that's what we've just done, we actually just drew a quadratic curve, in steps,
rather than in a single go. One of the fascinating parts about Bézier curves is that they can both be described
in terms of polynomial functions, as well as in terms of very simple interpolations of interpolations of [...].
That it turn means we can look at what these curves can do based on both "real maths" (by examining the functions,
That, in turn, means we can look at what these curves can do based on both "real maths" (by examining the functions,
their derivatives, and all that stuff), as well as by looking at the "mechanical" composition (which tells us
that a curve will never extend beyond the points we used to construct it, for instance)</p>