mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-08-17 22:11:38 +02:00
control section
This commit is contained in:
@@ -9,12 +9,45 @@ var Article = React.createClass({
|
||||
};
|
||||
},
|
||||
|
||||
setSectionId: function(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];
|
||||
return <Type key={name} ref={name} name={name} number={entry}/>;
|
||||
},
|
||||
|
||||
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 = Object.keys(this.state.sections).map((name, entry) => {
|
||||
var Type = this.state.sections[name];
|
||||
return <Type key={name} number={1+entry} />;
|
||||
});
|
||||
return <div>{ sections }</div>;
|
||||
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>);
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -26,7 +26,7 @@ var Graphic = React.createClass({
|
||||
render: function() {
|
||||
var href = "data:text/plain," + this.props.code;
|
||||
return (
|
||||
<figure>
|
||||
<figure className={this.props.inline ? "inline": false}>
|
||||
<canvas ref="canvas"
|
||||
onMouseDown={this.mouseDown}
|
||||
onMouseMove={this.mouseMove}
|
||||
@@ -45,8 +45,14 @@ var Graphic = React.createClass({
|
||||
this.cvs = cvs;
|
||||
var ctx = cvs.getContext("2d");
|
||||
this.ctx = ctx;
|
||||
this.props.setup(this);
|
||||
this.props.draw(this,this.curve);
|
||||
|
||||
if (this.props.setup) {
|
||||
this.props.setup(this);
|
||||
}
|
||||
|
||||
if (this.props.draw) {
|
||||
this.props.draw(this,this.curve);
|
||||
}
|
||||
},
|
||||
|
||||
mouseDown: function(evt) {
|
||||
@@ -81,7 +87,10 @@ var Graphic = React.createClass({
|
||||
this.mp.x = this.cx + this.ox;
|
||||
this.mp.y = this.cy + this.oy;
|
||||
this.curve.update();
|
||||
this.props.draw(this, this.curve);
|
||||
|
||||
if (this.props.draw) {
|
||||
this.props.draw(this, this.curve);
|
||||
}
|
||||
},
|
||||
|
||||
mouseUp: function(evt) {
|
||||
@@ -146,6 +155,10 @@ var Graphic = React.createClass({
|
||||
this.ctx.strokeStyle = c;
|
||||
},
|
||||
|
||||
getColor: function() {
|
||||
return this.ctx.strokeStyle || "black";
|
||||
},
|
||||
|
||||
setWeight: function(c) {
|
||||
this.ctx.lineWidth = c;
|
||||
},
|
||||
@@ -155,8 +168,8 @@ var Graphic = React.createClass({
|
||||
},
|
||||
|
||||
setRandomColor: function() {
|
||||
var r = ((255*Math.random())|0),
|
||||
g = ((255*Math.random())|0),
|
||||
var r = ((205*Math.random())|0),
|
||||
g = ((155*Math.random())|0),
|
||||
b = ((255*Math.random())|0);
|
||||
this.ctx.strokeStyle = "rgb("+r+","+g+","+b+")";
|
||||
},
|
||||
@@ -173,6 +186,10 @@ var Graphic = React.createClass({
|
||||
this.ctx.fillStyle = c;
|
||||
},
|
||||
|
||||
getFill: function() {
|
||||
return this.ctx.fillStyle || "transparent";
|
||||
},
|
||||
|
||||
noFill: function() {
|
||||
this.ctx.fillStyle = "transparent";
|
||||
},
|
||||
@@ -194,10 +211,23 @@ var Graphic = React.createClass({
|
||||
var pts = curve.points;
|
||||
this.setFill("black");
|
||||
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) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x + this.offset.x;
|
||||
@@ -315,7 +345,7 @@ var Graphic = React.createClass({
|
||||
this.ctx.stroke();
|
||||
},
|
||||
|
||||
drawText: function(text, offset) {
|
||||
text: function(text, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
if (this.offset) {
|
||||
offset.x += this.offset.x;
|
||||
|
@@ -1,34 +1,31 @@
|
||||
var React = require("react");
|
||||
var ReactDOM = require("react-dom");
|
||||
var noop = require("../lib/noop");
|
||||
|
||||
var MathJax = (typeof window !== "undefined" ? window.MathJax : false);
|
||||
|
||||
var noop = function() {};
|
||||
|
||||
// fallback will simply do nothing when typesetting.
|
||||
if(!MathJax){MathJax={Hub:{Queue:noop}};}
|
||||
|
||||
var LaTeX = React.createClass({
|
||||
mixins: [
|
||||
require("react-component-visibility")
|
||||
],
|
||||
|
||||
componentDidMount: function() {
|
||||
this.setComponentVisbilityRateLimit(200);
|
||||
getInitialState() {
|
||||
var data = this.props.children;
|
||||
if (!data.forEach) data = [data];
|
||||
return { latex: data.join('') };
|
||||
},
|
||||
|
||||
componentVisibilityChanged: function() {
|
||||
var visible = this.state.visible;
|
||||
if (visible) {
|
||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.refs.latex, false]);
|
||||
this.componentVisibilityChanged = noop;
|
||||
}
|
||||
componentDidMount: function() {
|
||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.refs.latex, false]);
|
||||
MathJax.Hub.Queue(this.bindHTML);
|
||||
},
|
||||
|
||||
bindHTML: function() {
|
||||
this.setState({ html: this.refs.latex.innerHTML });
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var data = this.props.children;
|
||||
if (!data.forEach) data = [data];
|
||||
return <p ref="latex" dangerouslySetInnerHTML={{__html: data.join('') }} />;
|
||||
var content = this.state.html ? this.state.html : this.state.latex;
|
||||
return <p ref="latex" dangerouslySetInnerHTML={{__html: content }} />;
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -3,7 +3,7 @@ var ReactDOM = require("react-dom");
|
||||
|
||||
var SectionHeader = React.createClass({
|
||||
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>;
|
||||
}
|
||||
});
|
||||
|
||||
|
261
components/sections/control/index.js
Normal file
261
components/sections/control/index.js
Normal 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<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>
|
||||
|
||||
|
||||
**/
|
||||
|
@@ -25,7 +25,7 @@ module.exports = {
|
||||
sketch.drawPoint(p, offset);
|
||||
var modulo = t % 1;
|
||||
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,
|
||||
y: offset.y + 1.25 * h4 * Math.sin(t) + 5
|
||||
});
|
||||
|
@@ -4,6 +4,10 @@ var SectionHeader = require("../../SectionHeader.jsx");
|
||||
var LaTeX = require("../../LaTeX.jsx");
|
||||
|
||||
var Explanation = React.createClass({
|
||||
statics: {
|
||||
title: "The basics of Bézier curves"
|
||||
},
|
||||
|
||||
circle: require("./circle"),
|
||||
|
||||
componentWillMount: function() {
|
||||
@@ -14,7 +18,7 @@ var Explanation = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<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
|
||||
functions are cheats: a "function" is actually a well defined term representing a mapping
|
||||
|
@@ -6,12 +6,12 @@ module.exports = {
|
||||
preface: require("./preface"),
|
||||
introduction: require("./introduction"),
|
||||
whatis: require("./whatis"),
|
||||
explanation: require("./explanation")
|
||||
explanation: require("./explanation"),
|
||||
control: require("./control")
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
control: require("./control.jsx"),
|
||||
|
||||
matrix: require("./matrix.jsx"),
|
||||
decasteljau: require("./decasteljau.jsx"),
|
||||
|
@@ -2,8 +2,11 @@ var React = require("react");
|
||||
var Graphic = require("../../Graphic.jsx");
|
||||
var SectionHeader = require("../../SectionHeader.jsx");
|
||||
|
||||
|
||||
var Introduction = React.createClass({
|
||||
statics: {
|
||||
title: "A lightning introduction"
|
||||
},
|
||||
|
||||
drawQuadratic: function(api) {
|
||||
var curve = api.getDefaultQuadratic();
|
||||
api.setCurve(curve);
|
||||
@@ -23,7 +26,7 @@ var Introduction = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<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
|
||||
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,
|
||||
and see how their shape changes based on what you do.</p>
|
||||
|
||||
<Graphic title="Quadratic Bézier curves" setup={ this.drawQuadratic } draw={ this.drawCurve }/>
|
||||
|
||||
<Graphic title="Cubic Bézier curves" setup={ this.drawCubic } draw={ this.drawCurve }/>
|
||||
<div className="figure">
|
||||
<Graphic inline={true} title="Quadratic Bézier curves" setup={ this.drawQuadratic } 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)
|
||||
applications, as well as in graphic design programs like Adobe Illustrator and Photoshop, Inkscape,
|
||||
|
@@ -1,10 +1,14 @@
|
||||
var React = require("react");
|
||||
|
||||
var Preface = React.createClass({
|
||||
statics: {
|
||||
title: "Preface"
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<section>
|
||||
<h2>{Preface.title}</h2>
|
||||
|
||||
<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
|
||||
@@ -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
|
||||
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
|
||||
of mathematical functions investigated by
|
||||
<a href="https://en.wikipedia.org/wiki/Sergei_Natanovich_Bernstein">Sergei Natanovich Bernstein</a>,
|
||||
of mathematical functions investigated
|
||||
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
|
||||
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
|
||||
|
@@ -5,6 +5,10 @@ var LaTeX = require("../../LaTeX.jsx");
|
||||
|
||||
|
||||
var Whatis = React.createClass({
|
||||
statics: {
|
||||
title: "So what makes a Bézier Curve?"
|
||||
},
|
||||
|
||||
interpolation: require("./interpolation"),
|
||||
|
||||
componentWillMount: function() {
|
||||
@@ -15,46 +19,50 @@ var Whatis = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<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
|
||||
what <em>are</em> Bézier curves, really?</p>
|
||||
|
||||
<p>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 uses really simple maths. So... let's start
|
||||
with the simple explanation:</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
|
||||
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
|
||||
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
|
||||
linear interpolation. It's simply "picking a point between two, points". If we know the distance
|
||||
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
|
||||
really easily:</p>
|
||||
linear interpolation. It's simply "picking a point between two, points".</p>
|
||||
|
||||
<p>If we know the distance 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 really easily:</p>
|
||||
|
||||
<LaTeX>\[
|
||||
p_1 = some\ point, \\
|
||||
p_2 = some\ other\ point, \\
|
||||
distance = (p_2 - p_1), \\
|
||||
ratio = \frac{percentage}{100}, \\
|
||||
new\ point = p_1 + distance \cdot ratio
|
||||
Given \left (
|
||||
\begin{align}
|
||||
p_1 &= some\ point \\
|
||||
p_2 &= some\ other\ point \\
|
||||
distance &= (p_2 - p_1) \\
|
||||
ratio &= \frac{percentage}{100} \\
|
||||
\end{align}
|
||||
\right ),\ our\ new\ point = p_1 + distance \cdot ratio
|
||||
|
||||
\]</LaTeX>
|
||||
|
||||
<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
|
||||
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
|
||||
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}/>
|
||||
|
||||
<p>And that brings us to the complicated maths: calculus. 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, 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>And that brings us to the complicated maths: calculus.</p>
|
||||
|
||||
<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,
|
||||
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
|
||||
can derive from those, and the various things we can do to, and with, Bézier curves.</p>
|
||||
|
@@ -62,7 +62,7 @@ module.exports = {
|
||||
sketch.setColor("black");
|
||||
sketch.setFill("black");
|
||||
sketch.drawSkeleton(curve);
|
||||
sketch.drawCurve(curve);
|
||||
//sketch.drawCurve(curve);
|
||||
|
||||
// draw 20% off-start points and struts
|
||||
sketch.setWeight(2);
|
||||
@@ -85,7 +85,7 @@ module.exports = {
|
||||
sketch.drawCircle(p1e3,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
|
||||
sketch.setColor("black");
|
||||
@@ -94,7 +94,7 @@ module.exports = {
|
||||
sketch.drawLine({x:0, y:0}, {x:0, y:this.dim});
|
||||
|
||||
sketch.drawSkeleton(curve);
|
||||
sketch.drawCurve(curve);
|
||||
//sketch.drawCurve(curve);
|
||||
|
||||
sketch.setColor("rgb(100,100,200)");
|
||||
sketch.drawLine(p1e, p2e);
|
||||
@@ -124,7 +124,7 @@ module.exports = {
|
||||
sketch.drawLine(p1e3, m3);
|
||||
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
|
||||
sketch.setColor("black");
|
||||
@@ -133,11 +133,18 @@ module.exports = {
|
||||
sketch.drawLine({x:0, y:0}, {x:0, y:this.dim});
|
||||
|
||||
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(m2,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});
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user