1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-09-29 17:49:08 +02:00

control section

This commit is contained in:
Pomax
2015-12-29 16:59:12 -08:00
parent fd0d07a4be
commit 54fb8b2cef
17 changed files with 1403 additions and 566 deletions

File diff suppressed because one or more lines are too long

View File

@@ -9,12 +9,45 @@ var Article = React.createClass({
}; };
}, },
render: function() { setSectionId: function(name, entry) {
var sections = Object.keys(this.state.sections).map((name, entry) => { ReactDOM.findDOMNode(this.refs[name]).setAttribute("id", name);
},
componentDidMount: function() {
// Not sure why this doesn't just work by passing a props.id
this.sectionMap(this.setSectionId);
},
sectionMap: function(mapping) {
return this.getSectionNames().map(mapping);
},
getSectionNames: function() {
return Object.keys(this.state.sections);
},
generateSection: function(name, entry) {
var Type = this.state.sections[name]; var Type = this.state.sections[name];
return <Type key={name} number={1+entry} />; return <Type key={name} ref={name} name={name} number={entry}/>;
}); },
return <div>{ sections }</div>;
generateNavItem: function(name, entry) {
var Type = this.state.sections[name];
return <li data-number={entry}><a href={'#' + name}>{Type.title || name}</a></li>;
},
render: function() {
var sections = this.sectionMap(this.generateSection);
return (<div>
<div ref="navigation">
<navigation>
<ul className="navigation">
{ this.sectionMap(this.generateNavItem) }
</ul>
</navigation>
</div>
<div ref="sections">{ sections }</div>
</div>);
} }
}); });

View File

@@ -26,7 +26,7 @@ var Graphic = React.createClass({
render: function() { render: function() {
var href = "data:text/plain," + this.props.code; var href = "data:text/plain," + this.props.code;
return ( return (
<figure> <figure className={this.props.inline ? "inline": false}>
<canvas ref="canvas" <canvas ref="canvas"
onMouseDown={this.mouseDown} onMouseDown={this.mouseDown}
onMouseMove={this.mouseMove} onMouseMove={this.mouseMove}
@@ -45,8 +45,14 @@ var Graphic = React.createClass({
this.cvs = cvs; this.cvs = cvs;
var ctx = cvs.getContext("2d"); var ctx = cvs.getContext("2d");
this.ctx = ctx; this.ctx = ctx;
if (this.props.setup) {
this.props.setup(this); this.props.setup(this);
}
if (this.props.draw) {
this.props.draw(this,this.curve); this.props.draw(this,this.curve);
}
}, },
mouseDown: function(evt) { mouseDown: function(evt) {
@@ -81,7 +87,10 @@ var Graphic = React.createClass({
this.mp.x = this.cx + this.ox; this.mp.x = this.cx + this.ox;
this.mp.y = this.cy + this.oy; this.mp.y = this.cy + this.oy;
this.curve.update(); this.curve.update();
if (this.props.draw) {
this.props.draw(this, this.curve); this.props.draw(this, this.curve);
}
}, },
mouseUp: function(evt) { mouseUp: function(evt) {
@@ -146,6 +155,10 @@ var Graphic = React.createClass({
this.ctx.strokeStyle = c; this.ctx.strokeStyle = c;
}, },
getColor: function() {
return this.ctx.strokeStyle || "black";
},
setWeight: function(c) { setWeight: function(c) {
this.ctx.lineWidth = c; this.ctx.lineWidth = c;
}, },
@@ -155,8 +168,8 @@ var Graphic = React.createClass({
}, },
setRandomColor: function() { setRandomColor: function() {
var r = ((255*Math.random())|0), var r = ((205*Math.random())|0),
g = ((255*Math.random())|0), g = ((155*Math.random())|0),
b = ((255*Math.random())|0); b = ((255*Math.random())|0);
this.ctx.strokeStyle = "rgb("+r+","+g+","+b+")"; this.ctx.strokeStyle = "rgb("+r+","+g+","+b+")";
}, },
@@ -173,6 +186,10 @@ var Graphic = React.createClass({
this.ctx.fillStyle = c; this.ctx.fillStyle = c;
}, },
getFill: function() {
return this.ctx.fillStyle || "transparent";
},
noFill: function() { noFill: function() {
this.ctx.fillStyle = "transparent"; this.ctx.fillStyle = "transparent";
}, },
@@ -194,10 +211,23 @@ var Graphic = React.createClass({
var pts = curve.points; var pts = curve.points;
this.setFill("black"); this.setFill("black");
pts.forEach(p => { pts.forEach(p => {
this.drawText(`(${p.x},${p.y})`, {x:p.x + 5, y:p.y + 10}); this.text(`(${p.x},${p.y})`, {x:p.x + 5, y:p.y + 10});
}); });
}, },
drawFunction: function(generator, offset) {
var p0 = generator(0),
plast = generator(1),
step = generator.step || 0.01,
p, t;
for (t=step; t<1.0; t+=step) {
p = generator(t);
this.drawLine(p0, p, offset);
p0 = p;
}
this.drawLine(p, plast, offset);
},
drawCurve: function(curve, offset) { drawCurve: function(curve, offset) {
offset = offset || { x:0, y:0 }; offset = offset || { x:0, y:0 };
var ox = offset.x + this.offset.x; var ox = offset.x + this.offset.x;
@@ -315,7 +345,7 @@ var Graphic = React.createClass({
this.ctx.stroke(); this.ctx.stroke();
}, },
drawText: function(text, offset) { text: function(text, offset) {
offset = offset || { x:0, y:0 }; offset = offset || { x:0, y:0 };
if (this.offset) { if (this.offset) {
offset.x += this.offset.x; offset.x += this.offset.x;

View File

@@ -1,34 +1,31 @@
var React = require("react"); var React = require("react");
var ReactDOM = require("react-dom"); var ReactDOM = require("react-dom");
var noop = require("../lib/noop");
var MathJax = (typeof window !== "undefined" ? window.MathJax : false); var MathJax = (typeof window !== "undefined" ? window.MathJax : false);
var noop = function() {};
// fallback will simply do nothing when typesetting. // fallback will simply do nothing when typesetting.
if(!MathJax){MathJax={Hub:{Queue:noop}};} if(!MathJax){MathJax={Hub:{Queue:noop}};}
var LaTeX = React.createClass({ var LaTeX = React.createClass({
mixins: [ getInitialState() {
require("react-component-visibility") var data = this.props.children;
], if (!data.forEach) data = [data];
return { latex: data.join('') };
componentDidMount: function() {
this.setComponentVisbilityRateLimit(200);
}, },
componentVisibilityChanged: function() { componentDidMount: function() {
var visible = this.state.visible;
if (visible) {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.refs.latex, false]); MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.refs.latex, false]);
this.componentVisibilityChanged = noop; MathJax.Hub.Queue(this.bindHTML);
} },
bindHTML: function() {
this.setState({ html: this.refs.latex.innerHTML });
}, },
render: function() { render: function() {
var data = this.props.children; var content = this.state.html ? this.state.html : this.state.latex;
if (!data.forEach) data = [data]; return <p ref="latex" dangerouslySetInnerHTML={{__html: content }} />;
return <p ref="latex" dangerouslySetInnerHTML={{__html: data.join('') }} />;
} }
}); });

View File

@@ -3,7 +3,7 @@ var ReactDOM = require("react-dom");
var SectionHeader = React.createClass({ var SectionHeader = React.createClass({
render: function() { render: function() {
return <h2 data-num={this.props.number}>{this.props.children}</h2>; return <h2 data-num={this.props.number}><a href={'#' + this.props.name}>{this.props.children}</a></h2>;
} }
}); });

View File

@@ -0,0 +1,261 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var LaTeX = require("../../LaTeX.jsx");
var Control = React.createClass({
statics: {
title: "Controlling Bézier curvatures"
},
drawCubic: function(api) {
var curve = api.getDefaultCubic();
api.setCurve(curve);
},
drawCurve: function(api, curve) {
api.reset();
api.drawSkeleton(curve);
api.drawCurve(curve);
},
drawGraphBasics: function(api, dim, pad, fwh) {
api.drawLine({x:pad, y:pad}, {x:dim-pad, y:pad});
api.drawLine({x:pad, y:pad}, {x:pad, y:dim-pad});
api.setFill("black");
api.text("t →", {x:dim/2, y: 15});
api.text("0", {x:pad, y: 15});
api.text("1", {x:dim-pad, y: 15});
api.text("S", {x:5, y: dim/2-pad});
api.text("↓", {x:5, y: dim/2});
api.text("0%", {x:4, y: pad+5});
api.text("100%", {x:2, y: dim-pad+10});
},
drawFunction: function(api, label, where, generator) {
api.setRandomColor();
api.drawFunction(generator);
api.setFill(api.getColor());
if (!!label) api.text(label, where);
},
drawQuadraticLerp: function(api) {
var dim = api.getPanelWidth(),
pad = 20,
fwh = dim - pad*2;
this.drawGraphBasics(api, dim, pad, fwh);
this.drawFunction(api, "first term", {x: pad*2, y: fwh}, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * (1-t) * (1-t)
};
});
this.drawFunction(api, "second term", {x: dim/2 - 1.5*pad, y: dim/2 + pad}, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * 2 * (1-t) * (t)
};
});
this.drawFunction(api, "third term", {x: fwh - pad*2.5, y: fwh}, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * (t) * (t)
};
});
},
drawCubicLerp: function(api) {
var dim = api.getPanelWidth(),
pad = 20,
fwh = dim - pad*2;
this.drawGraphBasics(api, dim, pad, fwh);
this.drawFunction(api, "first term", {x: pad*2, y: fwh}, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * (1-t) * (1-t) * (1-t)
};
});
this.drawFunction(api, "second term", {x: dim/2 - 4*pad, y: dim/2 }, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * 3 * (1-t) * (1-t) * (t)
};
});
this.drawFunction(api, "third term", {x: dim/2 + 2*pad, y: dim/2}, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * 3 * (1-t) * (t) * (t)
};
});
this.drawFunction(api, "fourth term", {x: fwh - pad*2.5, y: fwh}, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * (t) * (t) * (t)
};
});
},
draw15thLerp: function(api) {
var dim = api.getPanelWidth(),
pad = 20,
fwh = dim - pad*2;
this.drawGraphBasics(api, dim, pad, fwh);
var factor = [1,15,105,455,1365,3003,5005,6435,6435,5005,3003,1365,455,105,15,1]
for(var n=0; n<=15; n++) {
var label = false, position = false;
if (n===0) {
label = "first term";
position = {x: pad + 5, y: fwh};
}
if (n===15) {
label = "last term";
position = {x: dim - 3.5*pad, y: fwh};
}
this.drawFunction(api, label, position, function(t) {
return {
x: pad + t * fwh,
y: pad + fwh * factor[n] * Math.pow(1-t, 15-n) * Math.pow(t, n)
};
});
}
},
render: function() {
return (
<section>
<SectionHeader {...this.props}>{ Control.title }</SectionHeader>
<p>Bézier curves are (like all "splines") interpolation functions, meaning they take a set of
points, and generate values somewhere "between" those points. (One of the consequences of this
is that you'll never be able to generate a point that lies outside the outline for the control
points, commonly called the "hull" for the curve. Useful information!). In fact, we can visualize
how each point contributes to the value generated by the function, so we can see which points are
important, where, in the curve.</p>
<p>The following graphs show the interpolation functions for quadratic and cubic curves, with "S"
being the strength of a point's contribution to the total sum of the Bézier function. Click or
click-drag to see the interpolation percentages for each curve-defining point at a
specific <i>t</i> value.</p>
<div className="figure">
<Graphic inline={true} preset="simple" title="Quadratic interpolations" draw={this.drawQuadraticLerp}/>
<Graphic inline={true} preset="simple" title="Cubic interpolations" draw={this.drawCubicLerp}/>
<Graphic inline={true} preset="simple" title="15th order interpolations" draw={this.draw15thLerp}/>
</div>
<p>Also shown is the interpolation function for a 15<sup>th</sup> order Bézier function. As you can see,
the start and end point contribute considerably more to the curve's shape than any other point
in the control point set.</p>
<p>If we want to change the curve, we need to change the weights of each point, effectively changing
the interpolations. The way to do this is about as straight forward as possible: just multiply each
point with a value that changes its strength. These values are conventionally called "Weights", and
we can add them to our original Bézier function:</p>
<LaTeX>\[
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}}
\]</LaTeX>
<p>That looks complicated, but as it so happens, the "weights" are actually just the coordinate values
we want our curve to have: for an <i>n<sup>th</sup></i> order curve, w<sub>0</sub> is our start coordinate,
w<sub>n</sub> is our last coordinate, and everything in between is a controlling coordinate. Say we want
a cubic curve that starts at (120,160), is controlled by (35,200) and (220,260) and ends at (220,40),
we use this Bézier curve:</p>
<LaTeX>\[
\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. \]</LaTeX>
<p>Which gives us the curve we saw at the top of the article:</p>
<Graphic preset="simple" title="Our cubic Bézier curve" setup={this.drawCubic} draw={this.drawCurve}/>
<p>What else can we do with Bézier curves? Quite a lot, actually. The rest of this article covers
a multitude of possible operations and algorithms that we can apply, and the tasks they achieve.</p>
<div className="howtocode">
<h3>How to implement the weighted basis function</h3>
<p>Given that we already know how to implement basis function, adding in the control points
is remarkably easy:</p>
<pre>function Bezier(n,t,w[]):
sum = 0
for(k=0; k&lt;n; k++):
sum += w[k] * binomial(n,k) * (1-t)^(n-k) * t^(k)
return sum</pre>
<p>And for the extremely optimized versions:</p>
<pre>function Bezier(2,t,w[]):
t2 = t * t
mt = 1-t
mt2 = mt * mt
return w[0]*mt2 + w[1]*2*mt*t + w[2]*t2
function Bezier(3,t,w[]):
t2 = t * t
t3 = t2 * t
mt = 1-t
mt2 = mt * mt
mt3 = mt2 * mt
return w[0]*mt3 + 3*w[1]*mt2*t + 3*w[2]*mt*t2 + w[3]*t3</pre>
<p>And now we know how to program the weighted basis function.</p>
</div>
</section>
);
}
});
module.exports = Control;
/**
<textarea class="sketch-code" data-sketch-preset="ratios" data-sketch-title="Quadratic interpolations">
int order = 3;
</textarea>
<textarea class="sketch-code" data-sketch-preset="ratios" data-sketch-title="Cubic interpolations">
int order = 4;
</textarea>
<textarea class="sketch-code" data-sketch-preset="ratios" data-sketch-title="15th order interpolations">
int order = 15;
</textarea>
**/
/**
void setupCurve() {
setupDefaultCubic();
}
void drawCurve(BezierCurve curve) {
curve.draw();
}</Graphic>
**/

View File

@@ -25,7 +25,7 @@ module.exports = {
sketch.drawPoint(p, offset); sketch.drawPoint(p, offset);
var modulo = t % 1; var modulo = t % 1;
if(modulo<0.05 || modulo> 0.95) { if(modulo<0.05 || modulo> 0.95) {
sketch.drawText("t = " + Math.round(t), { sketch.text("t = " + Math.round(t), {
x: offset.x + 1.25 * w4 * Math.cos(t) - 10, x: offset.x + 1.25 * w4 * Math.cos(t) - 10,
y: offset.y + 1.25 * h4 * Math.sin(t) + 5 y: offset.y + 1.25 * h4 * Math.sin(t) + 5
}); });

View File

@@ -4,6 +4,10 @@ var SectionHeader = require("../../SectionHeader.jsx");
var LaTeX = require("../../LaTeX.jsx"); var LaTeX = require("../../LaTeX.jsx");
var Explanation = React.createClass({ var Explanation = React.createClass({
statics: {
title: "The basics of Bézier curves"
},
circle: require("./circle"), circle: require("./circle"),
componentWillMount: function() { componentWillMount: function() {
@@ -14,7 +18,7 @@ var Explanation = React.createClass({
render: function() { render: function() {
return ( return (
<section> <section>
<SectionHeader {...this.props}>The basics of Bézier curves?</SectionHeader> <SectionHeader {...this.props}>{ Explanation.title }</SectionHeader>
<p>Bézier curves are a form of "parametric" function. Mathematically speaking, parametric <p>Bézier curves are a form of "parametric" function. Mathematically speaking, parametric
functions are cheats: a "function" is actually a well defined term representing a mapping functions are cheats: a "function" is actually a well defined term representing a mapping

View File

@@ -6,12 +6,12 @@ module.exports = {
preface: require("./preface"), preface: require("./preface"),
introduction: require("./introduction"), introduction: require("./introduction"),
whatis: require("./whatis"), whatis: require("./whatis"),
explanation: require("./explanation") explanation: require("./explanation"),
control: require("./control")
}; };
/* /*
control: require("./control.jsx"),
matrix: require("./matrix.jsx"), matrix: require("./matrix.jsx"),
decasteljau: require("./decasteljau.jsx"), decasteljau: require("./decasteljau.jsx"),

View File

@@ -2,8 +2,11 @@ var React = require("react");
var Graphic = require("../../Graphic.jsx"); var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx"); var SectionHeader = require("../../SectionHeader.jsx");
var Introduction = React.createClass({ var Introduction = React.createClass({
statics: {
title: "A lightning introduction"
},
drawQuadratic: function(api) { drawQuadratic: function(api) {
var curve = api.getDefaultQuadratic(); var curve = api.getDefaultQuadratic();
api.setCurve(curve); api.setCurve(curve);
@@ -23,7 +26,7 @@ var Introduction = React.createClass({
render: function() { render: function() {
return ( return (
<section> <section>
<SectionHeader {...this.props}>A lightning introduction</SectionHeader> <SectionHeader {...this.props}>{Introduction.title}</SectionHeader>
<p>Let's start with the good stuff: when we're talking about Bézier curves, we're talking about the <p>Let's start with the good stuff: when we're talking about Bézier curves, we're talking about the
things that you can see in the following graphics. They run from some start point to some end point, things that you can see in the following graphics. They run from some start point to some end point,
@@ -31,9 +34,10 @@ var Introduction = React.createClass({
graphics on this page are interactive, go manipulate those curves a bit: click-drag the points, graphics on this page are interactive, go manipulate those curves a bit: click-drag the points,
and see how their shape changes based on what you do.</p> and see how their shape changes based on what you do.</p>
<Graphic title="Quadratic Bézier curves" setup={ this.drawQuadratic } draw={ this.drawCurve }/> <div className="figure">
<Graphic inline={true} title="Quadratic Bézier curves" setup={ this.drawQuadratic } draw={ this.drawCurve }/>
<Graphic title="Cubic Bézier curves" setup={ this.drawCubic } draw={ this.drawCurve }/> <Graphic inline={true} title="Cubic Bézier curves" setup={ this.drawCubic } draw={ this.drawCurve }/>
</div>
<p>These curves are used a lot in computer aided design and computer aided manufacturing (CAD/CAM) <p>These curves are used a lot in computer aided design and computer aided manufacturing (CAD/CAM)
applications, as well as in graphic design programs like Adobe Illustrator and Photoshop, Inkscape, applications, as well as in graphic design programs like Adobe Illustrator and Photoshop, Inkscape,

View File

@@ -1,10 +1,14 @@
var React = require("react"); var React = require("react");
var Preface = React.createClass({ var Preface = React.createClass({
statics: {
title: "Preface"
},
render: function() { render: function() {
return ( return (
<section> <section>
<h2>{Preface.title}</h2>
<p>In order to draw things in 2D, we usually rely on lines, which typically get classified <p>In order to draw things in 2D, we usually rely on lines, which typically get classified
into two categories: straight lines, and curves. The first of these are as easy to draw as they into two categories: straight lines, and curves. The first of these are as easy to draw as they
@@ -30,8 +34,8 @@ var Preface = React.createClass({
Citroën, coming up with a really elegant way of figuring out how to draw them. However, de Citroën, coming up with a really elegant way of figuring out how to draw them. However, de
Casteljau did not publish his work, making the question "who was first" hard to answer in Casteljau did not publish his work, making the question "who was first" hard to answer in
any absolute sense. Or is it? Bézier curves are, at their core, "Bernstein polynomials", a family any absolute sense. Or is it? Bézier curves are, at their core, "Bernstein polynomials", a family
of mathematical functions investigated by of mathematical functions investigated
<a href="https://en.wikipedia.org/wiki/Sergei_Natanovich_Bernstein">Sergei Natanovich Bernstein</a>, by <a href="https://en.wikipedia.org/wiki/Sergei_Natanovich_Bernstein">Sergei Natanovich Bernstein</a>,
with publications on them at least as far back as 1912. Anyway, that's mostly trivia, what with publications on them at least as far back as 1912. Anyway, that's mostly trivia, what
you are more likely to care about is that these curves are handy: you can link up multiple you are more likely to care about is that these curves are handy: you can link up multiple
Bézier curves so that the combination looks like a single curve. If you've ever drawn Photoshop Bézier curves so that the combination looks like a single curve. If you've ever drawn Photoshop

View File

@@ -5,6 +5,10 @@ var LaTeX = require("../../LaTeX.jsx");
var Whatis = React.createClass({ var Whatis = React.createClass({
statics: {
title: "So what makes a Bézier Curve?"
},
interpolation: require("./interpolation"), interpolation: require("./interpolation"),
componentWillMount: function() { componentWillMount: function() {
@@ -15,46 +19,50 @@ var Whatis = React.createClass({
render: function() { render: function() {
return ( return (
<section> <section>
<SectionHeader {...this.props}>What is a Bézier Curve?</SectionHeader> <SectionHeader {...this.props}>{Whatis.title}</SectionHeader>
<p>Playing with the points for curves may have given you a feel for how Bézier curves behaves, but <p>Playing with the points for curves may have given you a feel for how Bézier curves behaves, but
what <em>are</em> Bézier curves, really?</p> what <em>are</em> Bézier curves, really? There are two ways to explain what a Bézier curve is, and
they turn out to be the entirely equivalent, but one of them uses complicated maths, and the other
<p>There are two ways to explain what a Bézier curve is, and they turn out to be the entirely equivalent, uses really simple maths. So... let's start with the simple explanation:</p>
but one of them uses complicated maths, and the other uses really simple maths. So... let's start
with the simple explanation:</p>
<p>Bezier curves are the result of <a href="https://en.wikipedia.org/wiki/Linear_interpolation">linear <p>Bezier curves are the result of <a href="https://en.wikipedia.org/wiki/Linear_interpolation">linear
interpolations</a>. That sounds complicated but you've been doing linear interpolation since you were interpolations</a>. That sounds complicated but you've been doing linear interpolation since you were
very young: any time you had to point at something between two other things, you've been applying very young: any time you had to point at something between two other things, you've been applying
linear interpolation. It's simply "picking a point between two, points". If we know the distance linear interpolation. It's simply "picking a point between two, points".</p>
between those two points, and we want a new point that is, say, 20% the distance away from
the first point (and thus 80% the distance away from the second point) then we can compute that <p>If we know the distance between those two points, and we want a new point that is, say, 20% the
really easily:</p> distance away from the first point (and thus 80% the distance away from the second point) then we
can compute that really easily:</p>
<LaTeX>\[ <LaTeX>\[
p_1 = some\ point, \\ Given \left (
p_2 = some\ other\ point, \\ \begin{align}
distance = (p_2 - p_1), \\ p_1 &= some\ point \\
ratio = \frac{percentage}{100}, \\ p_2 &= some\ other\ point \\
new\ point = p_1 + distance \cdot ratio distance &= (p_2 - p_1) \\
ratio &= \frac{percentage}{100} \\
\end{align}
\right ),\ our\ new\ point = p_1 + distance \cdot ratio
\]</LaTeX> \]</LaTeX>
<p>So let's look at that in action: the following graphic is interactive in that you can use your <p>So let's look at that in action: the following graphic is interactive in that you can use your
'+' and '-' keys to increase or decrease the interpolation distance, to see what happens. We start '+' and '-' keys to increase or decrease the interpolation distance, to see what happens. We start
with three points, which gives us two lines. Linear interpolation over those lines gives use two with three points, which gives us two lines. Linear interpolation over those lines gives use two
points, between which we can again perform linear interpolation, yielding a single point. And that points, between which we can again perform linear interpolation, yielding a single point. And that
point, and all points we can form in this way for all distances taken together, form our Bézier curve:</p> point and all points we can form in this way for all distances taken together form our Bézier curve:</p>
<Graphic title="Linear Interpolation leading to Bézier curves" setup={this.setup} draw={this.draw}/> <Graphic title="Linear Interpolation leading to Bézier curves" setup={this.setup} draw={this.draw}/>
<p>And that brings us to the complicated maths: calculus. While it doesn't look like that's what we've just done, <p>And that brings us to the complicated maths: calculus.</p>
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 <p>While it doesn't look like that's what we've just done, we actually just drew a quadratic curve, in steps,
of very simple interpolations of interpolations of [...]. That it turn means we can look at what these curves rather than in a single go. One of the fascinating parts about Bézier curves is that they can both be described
can do based on both "real maths" (by examining the functions, their derivatives, and all that stuff), as well in terms of polynomial functions, as well as in terms of very simple interpolations of interpolations of [...].
as by looking at the "mechanical" composition (which tells us that a curve will never extend beyond the points That it turn means we can look at what these curves can do based on both "real maths" (by examining the functions,
we used to construct it, for instance)</p> 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>
<p>So let's start looking at Bézier curves a bit more in depth. Their mathematical expressions, the properties we <p>So let's start looking at Bézier curves a bit more in depth. Their mathematical expressions, the properties we
can derive from those, and the various things we can do to, and with, Bézier curves.</p> can derive from those, and the various things we can do to, and with, Bézier curves.</p>

View File

@@ -62,7 +62,7 @@ module.exports = {
sketch.setColor("black"); sketch.setColor("black");
sketch.setFill("black"); sketch.setFill("black");
sketch.drawSkeleton(curve); sketch.drawSkeleton(curve);
sketch.drawCurve(curve); //sketch.drawCurve(curve);
// draw 20% off-start points and struts // draw 20% off-start points and struts
sketch.setWeight(2); sketch.setWeight(2);
@@ -85,7 +85,7 @@ module.exports = {
sketch.drawCircle(p1e3,3); sketch.drawCircle(p1e3,3);
sketch.drawCircle(p2e3,3); sketch.drawCircle(p2e3,3);
sketch.drawText("First linear interpolation at 20% / 40% / 60%", {x:5, y:15}); sketch.text("First linear interpolation at 20% / 40% / 60%", {x:5, y:15});
// next panel // next panel
sketch.setColor("black"); sketch.setColor("black");
@@ -94,7 +94,7 @@ module.exports = {
sketch.drawLine({x:0, y:0}, {x:0, y:this.dim}); sketch.drawLine({x:0, y:0}, {x:0, y:this.dim});
sketch.drawSkeleton(curve); sketch.drawSkeleton(curve);
sketch.drawCurve(curve); //sketch.drawCurve(curve);
sketch.setColor("rgb(100,100,200)"); sketch.setColor("rgb(100,100,200)");
sketch.drawLine(p1e, p2e); sketch.drawLine(p1e, p2e);
@@ -124,7 +124,7 @@ module.exports = {
sketch.drawLine(p1e3, m3); sketch.drawLine(p1e3, m3);
sketch.drawCircle(m3,3); sketch.drawCircle(m3,3);
sketch.drawText("Second interpolation at 20% / 40% / 60%", {x:5, y:15}); sketch.text("Second interpolation at 20% / 40% / 60%", {x:5, y:15});
// next panel // next panel
sketch.setColor("black"); sketch.setColor("black");
@@ -133,11 +133,18 @@ module.exports = {
sketch.drawLine({x:0, y:0}, {x:0, y:this.dim}); sketch.drawLine({x:0, y:0}, {x:0, y:this.dim});
sketch.drawSkeleton(curve); sketch.drawSkeleton(curve);
sketch.drawCurve(curve); sketch.setColor("lightgrey");
for(var t=1,d=20,v,tvp; t<d; t++) {
v = t/d;
tvp = curve.get(v);
sketch.drawCircle(tvp,2);
}
sketch.setColor("black");
sketch.drawCircle(m,3); sketch.drawCircle(m,3);
sketch.drawCircle(m2,3); sketch.drawCircle(m2,3);
sketch.drawCircle(m3,3); sketch.drawCircle(m3,3);
sketch.drawText("Curve points for t = 0.2, t = 0.4, t = 0.6", {x:5, y:15}); sketch.text("Curve points for t = 0.2, t = 0.4, t = 0.6", {x:5, y:15});
} }
}; };

View File

@@ -42,6 +42,7 @@
<header> <header>
<h1>A Primer on Bézier Curves</h1> <h1>A Primer on Bézier Curves</h1>
<h2>A free, online book for when you really need to know how to do Bézier things.</h2>
</header> </header>
<!-- React does its magic here --> <!-- React does its magic here -->

4
lib/noop.js Normal file
View File

@@ -0,0 +1,4 @@
/**
* because somethings you just need a no-op
*/
module.exports = function noop() {};

View File

@@ -36,6 +36,7 @@
"file-loader": "^0.8.5", "file-loader": "^0.8.5",
"jsmin": "^1.0.1", "jsmin": "^1.0.1",
"json-stringify-safe": "^5.0.1", "json-stringify-safe": "^5.0.1",
"katex": "git+https://github.com/Khan/KaTeX.git",
"less": "^2.5.3", "less": "^2.5.3",
"less-loader": "^2.2.2", "less-loader": "^2.2.2",
"less-watch-compiler": "^1.1.4", "less-watch-compiler": "^1.1.4",

View File

@@ -12,7 +12,19 @@ header, section, footer {
margin: 0 auto; margin: 0 auto;
} }
header { font-size: 125%; } header {
text-align: center;
h1 {
font-size: 360%;
margin: 0;
margin-bottom: 1rem;
}
h2 {
font-size: 125%;
margin: 0;
}
margin-bottom: 2rem;
}
article { article {
width: 960px; width: 960px;
@@ -24,17 +36,23 @@ article {
padding: 1em; padding: 1em;
box-shadow: 25px 0px 25px 25px rgba(255, 255, 255, 0.74); box-shadow: 25px 0px 25px 25px rgba(255, 255, 255, 0.74);
} }
section p { section p {
text-align: justify; text-align: justify;
} }
section h2:before { content: "§" attr(data-num) " — "; } section h2 {
section *+h2:before { content: ""; } &[data-num] {
section * h2:before { content: ""; } &:before {
content: "§" attr(data-num) " — ";
}
}
section h2 { margin-top: 2em; cursor: pointer; } a,a:active,a:hover,a:visited {
section *+h2 { margin-top: 0em; cursor: auto; } text-decoration: none;
section * h2 { margin-top: 0em; cursor: auto; } color: inherit;
}
}
canvas.loading-sketch { canvas.loading-sketch {
background: url('../images/ajax-loader.gif'); background: url('../images/ajax-loader.gif');
@@ -229,11 +247,18 @@ figure {
@psize: 0.5em; @psize: 0.5em;
padding: @psize @psize 0 @psize; padding: @psize @psize 0 @psize;
text-align: center; text-align: center;
&.inline {
border: none;
margin: 0;
}
canvas { canvas {
display: inline-block; display: inline-block;
background: white; background: white;
border: 1px solid lightgrey; border: 1px solid lightgrey;
} }
figcaption { figcaption {
text-align: center; text-align: center;
padding: 0.5em 0; padding: 0.5em 0;
@@ -241,3 +266,31 @@ figure {
font-size: 90%; font-size: 90%;
} }
} }
div.figure {
display: inline-block;
border: 1px solid grey;
text-align: center;
}
/* =========================================================== */
navigation {
display: block;
width: 70%;
margin: 0 auto;
padding: 0;
ul {
background: #F2F2F9;
list-style: none;
margin: 0;
padding: 0.5em 1em;
li {
&:nth-child(n+2):before {
content: "§" attr(data-number) ". "
}
}
}
}