mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-08-22 16:23:12 +02:00
start of revision control
This commit is contained in:
6
components/App.jsx
Normal file
6
components/App.jsx
Normal file
@@ -0,0 +1,6 @@
|
||||
var React = require("react");
|
||||
var ReactDOM = require("react-dom");
|
||||
var Article = require("./Article.jsx");
|
||||
var style = require("../stylesheets/style.less");
|
||||
|
||||
ReactDOM.render(<Article/>, document.getElementById("article"));
|
21
components/Article.jsx
Normal file
21
components/Article.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
var React = require("react");
|
||||
var ReactDOM = require("react-dom");
|
||||
|
||||
var Article = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
sections: require("./sections")
|
||||
};
|
||||
},
|
||||
|
||||
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>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Article;
|
188
components/Figure.jsx
Normal file
188
components/Figure.jsx
Normal file
@@ -0,0 +1,188 @@
|
||||
function bindDrawFunctions(idx) {
|
||||
|
||||
var figure = find("figure")[idx];
|
||||
var cvs = document.createElement("canvas");
|
||||
cvs.width = 200;
|
||||
cvs.height = 200;
|
||||
var ctx = cvs.getContext("2d");
|
||||
figure.appendChild(cvs);
|
||||
var button = figure.querySelector("button");
|
||||
if(button) { figure.appendChild(button); }
|
||||
|
||||
return {
|
||||
getCanvas: function() { return cvs; },
|
||||
|
||||
reset: function() {
|
||||
cvs.width = cvs.width;
|
||||
ctx.strokeStyle = "black";
|
||||
ctx.fillStyle = "none";
|
||||
},
|
||||
|
||||
setColor: function(c) {
|
||||
ctx.strokeStyle = c;
|
||||
},
|
||||
|
||||
noColor: function(c) {
|
||||
ctx.strokeStyle = "transparent";
|
||||
},
|
||||
|
||||
setRandomColor: function() {
|
||||
var r = ((255*Math.random())|0),
|
||||
g = ((255*Math.random())|0),
|
||||
b = ((255*Math.random())|0);
|
||||
ctx.strokeStyle = "rgb("+r+","+g+","+b+")";
|
||||
},
|
||||
|
||||
setRandomFill: function(a) {
|
||||
a = (typeof a === "undefined") ? 1 : a;
|
||||
var r = ((255*Math.random())|0),
|
||||
g = ((255*Math.random())|0),
|
||||
b = ((255*Math.random())|0);
|
||||
ctx.fillStyle = "rgba("+r+","+g+","+b+","+a+")";
|
||||
},
|
||||
|
||||
setFill: function(c) {
|
||||
ctx.fillStyle = c;
|
||||
},
|
||||
|
||||
noFill: function() {
|
||||
ctx.fillStyle = "transparent";
|
||||
},
|
||||
|
||||
drawSkeleton: function(curve, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var pts = curve.points;
|
||||
ctx.strokeStyle = "lightgrey";
|
||||
this.drawLine(pts[0], pts[1], offset);
|
||||
if(pts.length === 3) { this.drawLine(pts[1], pts[2], offset); }
|
||||
else {this.drawLine(pts[2], pts[3], offset); }
|
||||
ctx.strokeStyle = "black";
|
||||
this.drawPoints(pts, offset);
|
||||
},
|
||||
|
||||
drawCurve: function(curve, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x;
|
||||
var oy = offset.y;
|
||||
ctx.beginPath();
|
||||
var p = curve.points, i;
|
||||
ctx.moveTo(p[0].x + ox, p[0].y + oy);
|
||||
if(p.length === 3) {
|
||||
ctx.quadraticCurveTo(
|
||||
p[1].x + ox, p[1].y + oy,
|
||||
p[2].x + ox, p[2].y + oy
|
||||
);
|
||||
}
|
||||
if(p.length === 4) {
|
||||
ctx.bezierCurveTo(
|
||||
p[1].x + ox, p[1].y + oy,
|
||||
p[2].x + ox, p[2].y + oy,
|
||||
p[3].x + ox, p[3].y + oy
|
||||
);
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
},
|
||||
|
||||
drawLine: function(p1, p2, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x;
|
||||
var oy = offset.y;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(p1.x + ox,p1.y + oy);
|
||||
ctx.lineTo(p2.x + ox,p2.y + oy);
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
drawPoint: function(p, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x;
|
||||
var oy = offset.y;
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x + ox, p.y + oy, 5, 0, 2*Math.PI);
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
drawPoints: function(points, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
points.forEach(function(p) {
|
||||
this.drawCircle(p, 3, offset);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
drawArc: function(p, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x;
|
||||
var oy = offset.y;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(p.x + ox, p.y + oy);
|
||||
ctx.arc(p.x + ox, p.y + oy, p.r, p.s, p.e);
|
||||
ctx.lineTo(p.x + ox, p.y + oy);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
drawCircle: function(p, r, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x;
|
||||
var oy = offset.y;
|
||||
ctx.beginPath();
|
||||
ctx.arc(p.x + ox, p.y + oy, r, 0, 2*Math.PI);
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
drawbbox: function(bbox, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x;
|
||||
var oy = offset.y;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(bbox.x.min + ox, bbox.y.min + oy);
|
||||
ctx.lineTo(bbox.x.min + ox, bbox.y.max + oy);
|
||||
ctx.lineTo(bbox.x.max + ox, bbox.y.max + oy);
|
||||
ctx.lineTo(bbox.x.max + ox, bbox.y.min + oy);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
drawShape: function(shape, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var order = shape.forward.points.length - 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(offset.x + shape.startcap.points[0].x, offset.y + shape.startcap.points[0].y);
|
||||
ctx.lineTo(offset.x + shape.startcap.points[3].x, offset.y + shape.startcap.points[3].y);
|
||||
if(order === 3) {
|
||||
ctx.bezierCurveTo(
|
||||
offset.x + shape.forward.points[1].x, offset.y + shape.forward.points[1].y,
|
||||
offset.x + shape.forward.points[2].x, offset.y + shape.forward.points[2].y,
|
||||
offset.x + shape.forward.points[3].x, offset.y + shape.forward.points[3].y
|
||||
);
|
||||
} else {
|
||||
ctx.quadraticCurveTo(
|
||||
offset.x + shape.forward.points[1].x, offset.y + shape.forward.points[1].y,
|
||||
offset.x + shape.forward.points[2].x, offset.y + shape.forward.points[2].y
|
||||
);
|
||||
}
|
||||
ctx.lineTo(offset.x + shape.endcap.points[3].x, offset.y + shape.endcap.points[3].y);
|
||||
if(order === 3) {
|
||||
ctx.bezierCurveTo(
|
||||
offset.x + shape.back.points[1].x, offset.y + shape.back.points[1].y,
|
||||
offset.x + shape.back.points[2].x, offset.y + shape.back.points[2].y,
|
||||
offset.x + shape.back.points[3].x, offset.y + shape.back.points[3].y
|
||||
);
|
||||
} else {
|
||||
ctx.quadraticCurveTo(
|
||||
offset.x + shape.back.points[1].x, offset.y + shape.back.points[1].y,
|
||||
offset.x + shape.back.points[2].x, offset.y + shape.back.points[2].y
|
||||
);
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
},
|
||||
|
||||
drawText: function(text, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
ctx.fillText(text, offset.x, offset.y);
|
||||
}
|
||||
};
|
||||
}
|
316
components/Graphic.jsx
Normal file
316
components/Graphic.jsx
Normal file
@@ -0,0 +1,316 @@
|
||||
var React = require("react");
|
||||
var ReactDOM = require("react-dom");
|
||||
|
||||
var fix = function(e) {
|
||||
e = e || window.event;
|
||||
var target = e.target || e.srcElement,
|
||||
rect = target.getBoundingClientRect();
|
||||
e.offsetX = e.clientX - rect.left;
|
||||
e.offsetY = e.clientY - rect.top;
|
||||
};
|
||||
|
||||
var defaultWidth = 275;
|
||||
var defaultHeight = 275;
|
||||
|
||||
var Graphic = React.createClass({
|
||||
Bezier: require("bezier-js"),
|
||||
curve: false,
|
||||
mx:0,
|
||||
my:0,
|
||||
cx:0,
|
||||
cy:0,
|
||||
mp: { x: 0, y: 0},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<figure>
|
||||
<canvas ref="canvas"
|
||||
onMouseDown={this.mouseDown}
|
||||
onMouseMove={this.mouseMove}
|
||||
onMouseUp={this.mouseUp}
|
||||
onClick={this.mouseClick}
|
||||
/>
|
||||
<figcaption>{this.props.title}</figcaption>
|
||||
</figure>
|
||||
);
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
var cvs = this.refs.canvas;
|
||||
cvs.width = defaultWidth;
|
||||
cvs.height = defaultHeight;
|
||||
this.cvs = cvs;
|
||||
var ctx = cvs.getContext("2d");
|
||||
this.ctx = ctx;
|
||||
this.props.setup(this);
|
||||
this.props.draw(this,this.curve);
|
||||
},
|
||||
|
||||
mouseDown: function(evt) {
|
||||
fix(evt);
|
||||
this.mx = evt.offsetX;
|
||||
this.my = evt.offsetY;
|
||||
this.lpts.forEach(p => {
|
||||
if(Math.abs(this.mx - p.x)<10 && Math.abs(this.my - p.y)<10) {
|
||||
this.moving = true;
|
||||
this.mp = p;
|
||||
this.cx = p.x;
|
||||
this.cy = p.y;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
mouseMove: function(evt) {
|
||||
fix(evt);
|
||||
var found = false;
|
||||
this.lpts.forEach(p => {
|
||||
var mx = evt.offsetX;
|
||||
var my = evt.offsetY;
|
||||
if(Math.abs(mx-p.x)<10 && Math.abs(my-p.y)<10) {
|
||||
found = found || true;
|
||||
}
|
||||
});
|
||||
this.cvs.style.cursor = found ? "pointer" : "default";
|
||||
|
||||
if(!this.moving) return;
|
||||
this.ox = evt.offsetX - this.mx;
|
||||
this.oy = evt.offsetY - this.my;
|
||||
this.mp.x = this.cx + this.ox;
|
||||
this.mp.y = this.cy + this.oy;
|
||||
this.curve.update();
|
||||
this.props.draw(this, this.curve);
|
||||
},
|
||||
|
||||
mouseUp: function(evt) {
|
||||
if(!this.moving) return;
|
||||
this.moving = false;
|
||||
this.mp = false;
|
||||
},
|
||||
|
||||
mouseClick: function(evt) {
|
||||
fix(evt);
|
||||
this.mx = evt.offsetX;
|
||||
this.my = evt.offsetY;
|
||||
},
|
||||
|
||||
/**
|
||||
* API for curve drawing.
|
||||
*/
|
||||
|
||||
reset: function() {
|
||||
this.refs.canvas.width = this.refs.canvas.width;
|
||||
this.ctx.strokeStyle = "black";
|
||||
this.ctx.lineWidth = 1;
|
||||
this.ctx.fillStyle = "none";
|
||||
this.offset = {x:0, y:0};
|
||||
},
|
||||
|
||||
getPanelWidth: function() {
|
||||
return defaultWidth;
|
||||
},
|
||||
|
||||
getPanelHeight: function() {
|
||||
return defaultHeight;
|
||||
},
|
||||
|
||||
getDefaultQuadratic: function() {
|
||||
return new this.Bezier(70,250,20,110,250,60);
|
||||
},
|
||||
|
||||
getDefaultCubic: function() {
|
||||
return new this.Bezier(120,160,35,200,220,260,220,40);
|
||||
},
|
||||
|
||||
setPanelCount: function(c) {
|
||||
var cvs = this.refs.canvas;
|
||||
cvs.width = c*defaultWidth;
|
||||
},
|
||||
|
||||
setCurve: function(c) {
|
||||
this.curve = c;
|
||||
this.lpts = c.points;
|
||||
},
|
||||
|
||||
setOffset: function(f) {
|
||||
this.offset = f;
|
||||
},
|
||||
|
||||
setColor: function(c) {
|
||||
this.ctx.strokeStyle = c;
|
||||
},
|
||||
|
||||
setWeight: function(c) {
|
||||
this.ctx.lineWidth = c;
|
||||
},
|
||||
|
||||
noColor: function(c) {
|
||||
this.ctx.strokeStyle = "transparent";
|
||||
},
|
||||
|
||||
setRandomColor: function() {
|
||||
var r = ((255*Math.random())|0),
|
||||
g = ((255*Math.random())|0),
|
||||
b = ((255*Math.random())|0);
|
||||
this.ctx.strokeStyle = "rgb("+r+","+g+","+b+")";
|
||||
},
|
||||
|
||||
setRandomFill: function(a) {
|
||||
a = (typeof a === "undefined") ? 1 : a;
|
||||
var r = ((255*Math.random())|0),
|
||||
g = ((255*Math.random())|0),
|
||||
b = ((255*Math.random())|0);
|
||||
this.ctx.fillStyle = "rgba("+r+","+g+","+b+","+a+")";
|
||||
},
|
||||
|
||||
setFill: function(c) {
|
||||
this.ctx.fillStyle = c;
|
||||
},
|
||||
|
||||
noFill: function() {
|
||||
this.ctx.fillStyle = "transparent";
|
||||
},
|
||||
|
||||
drawSkeleton: function(curve, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var pts = curve.points;
|
||||
this.ctx.strokeStyle = "lightgrey";
|
||||
this.drawLine(pts[0], pts[1], offset);
|
||||
if(pts.length === 3) { this.drawLine(pts[1], pts[2], offset); }
|
||||
else {this.drawLine(pts[2], pts[3], offset); }
|
||||
this.ctx.strokeStyle = "black";
|
||||
this.drawPoints(pts, offset);
|
||||
},
|
||||
|
||||
drawCurve: function(curve, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x + this.offset.x;
|
||||
var oy = offset.y + this.offset.y;
|
||||
this.ctx.beginPath();
|
||||
var p = curve.points, i;
|
||||
this.ctx.moveTo(p[0].x + ox, p[0].y + oy);
|
||||
if(p.length === 3) {
|
||||
this.ctx.quadraticCurveTo(
|
||||
p[1].x + ox, p[1].y + oy,
|
||||
p[2].x + ox, p[2].y + oy
|
||||
);
|
||||
}
|
||||
if(p.length === 4) {
|
||||
this.ctx.bezierCurveTo(
|
||||
p[1].x + ox, p[1].y + oy,
|
||||
p[2].x + ox, p[2].y + oy,
|
||||
p[3].x + ox, p[3].y + oy
|
||||
);
|
||||
}
|
||||
this.ctx.stroke();
|
||||
this.ctx.closePath();
|
||||
},
|
||||
|
||||
drawLine: function(p1, p2, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x + this.offset.x;
|
||||
var oy = offset.y + this.offset.y;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(p1.x + ox,p1.y + oy);
|
||||
this.ctx.lineTo(p2.x + ox,p2.y + oy);
|
||||
this.ctx.stroke();
|
||||
},
|
||||
|
||||
drawPoint: function(p, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x + this.offset.x;
|
||||
var oy = offset.y + this.offset.y;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(p.x + ox, p.y + oy, 5, 0, 2*Math.PI);
|
||||
this.ctx.stroke();
|
||||
},
|
||||
|
||||
drawPoints: function(points, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
points.forEach(function(p) {
|
||||
this.drawCircle(p, 3, offset);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
drawArc: function(p, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x + this.offset.x;
|
||||
var oy = offset.y + this.offset.y;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(p.x + ox, p.y + oy);
|
||||
this.ctx.arc(p.x + ox, p.y + oy, p.r, p.s, p.e);
|
||||
this.ctx.lineTo(p.x + ox, p.y + oy);
|
||||
this.ctx.fill();
|
||||
this.ctx.stroke();
|
||||
},
|
||||
|
||||
drawCircle: function(p, r, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x + this.offset.x;
|
||||
var oy = offset.y + this.offset.y;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(p.x + ox, p.y + oy, r, 0, 2*Math.PI);
|
||||
this.ctx.stroke();
|
||||
},
|
||||
|
||||
drawbbox: function(bbox, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x + this.offset.x;
|
||||
var oy = offset.y + this.offset.y;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(bbox.x.min + ox, bbox.y.min + oy);
|
||||
this.ctx.lineTo(bbox.x.min + ox, bbox.y.max + oy);
|
||||
this.ctx.lineTo(bbox.x.max + ox, bbox.y.max + oy);
|
||||
this.ctx.lineTo(bbox.x.max + ox, bbox.y.min + oy);
|
||||
this.ctx.closePath();
|
||||
this.ctx.stroke();
|
||||
},
|
||||
|
||||
drawShape: function(shape, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
var ox = offset.x + this.offset.x;
|
||||
var oy = offset.y + this.offset.y;
|
||||
var order = shape.forward.points.length - 1;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(ox + shape.startcap.points[0].x, oy + shape.startcap.points[0].y);
|
||||
this.ctx.lineTo(ox + shape.startcap.points[3].x, oy + shape.startcap.points[3].y);
|
||||
if(order === 3) {
|
||||
this.ctx.bezierCurveTo(
|
||||
ox + shape.forward.points[1].x, oy + shape.forward.points[1].y,
|
||||
ox + shape.forward.points[2].x, oy + shape.forward.points[2].y,
|
||||
ox + shape.forward.points[3].x, oy + shape.forward.points[3].y
|
||||
);
|
||||
} else {
|
||||
this.ctx.quadraticCurveTo(
|
||||
ox + shape.forward.points[1].x, oy + shape.forward.points[1].y,
|
||||
ox + shape.forward.points[2].x, oy + shape.forward.points[2].y
|
||||
);
|
||||
}
|
||||
this.ctx.lineTo(ox + shape.endcap.points[3].x, oy + shape.endcap.points[3].y);
|
||||
if(order === 3) {
|
||||
this.ctx.bezierCurveTo(
|
||||
ox + shape.back.points[1].x, oy + shape.back.points[1].y,
|
||||
ox + shape.back.points[2].x, oy + shape.back.points[2].y,
|
||||
ox + shape.back.points[3].x, oy + shape.back.points[3].y
|
||||
);
|
||||
} else {
|
||||
this.ctx.quadraticCurveTo(
|
||||
ox + shape.back.points[1].x, oy + shape.back.points[1].y,
|
||||
ox + shape.back.points[2].x, oy + shape.back.points[2].y
|
||||
);
|
||||
}
|
||||
this.ctx.closePath();
|
||||
this.ctx.fill();
|
||||
this.ctx.stroke();
|
||||
},
|
||||
|
||||
drawText: function(text, offset) {
|
||||
offset = offset || { x:0, y:0 };
|
||||
if (this.offset) {
|
||||
offset.x += this.offset.x;
|
||||
offset.y += this.offset.y;
|
||||
}
|
||||
this.ctx.fillText(text, offset.x, offset.y);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Graphic;
|
35
components/LaTeX.jsx
Normal file
35
components/LaTeX.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
var React = require("react");
|
||||
var ReactDOM = require("react-dom");
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
componentVisibilityChanged: function() {
|
||||
var visible = this.state.visible;
|
||||
if (visible) {
|
||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub, this.refs.latex, false]);
|
||||
this.componentVisibilityChanged = noop;
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var data = this.props.children;
|
||||
if (!data.forEach) data = [data];
|
||||
return <p ref="latex" dangerouslySetInnerHTML={{__html: data.join('') }} />;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = LaTeX;
|
10
components/SectionHeader.jsx
Normal file
10
components/SectionHeader.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
var React = require("react");
|
||||
var ReactDOM = require("react-dom");
|
||||
|
||||
var SectionHeader = React.createClass({
|
||||
render: function() {
|
||||
return <h2 data-num={this.props.number}>{this.props.children}</h2>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = SectionHeader;
|
38
components/sections/explanation/code.txt
Normal file
38
components/sections/explanation/code.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
void drawFunction() {
|
||||
pushStyle();
|
||||
translate(dim/2,dim/2);
|
||||
stroke(150);
|
||||
line(-dim,0,dim,0);
|
||||
line(0,-dim,0,dim);
|
||||
|
||||
stroke(0);
|
||||
float r = dim/3;
|
||||
for(float t=0; t<=5; t+=0.01) {
|
||||
point(r * sin(t), -r * cos(t));
|
||||
}
|
||||
|
||||
fill(0);
|
||||
|
||||
for(float i=0; i<=5; i+=0.5) {
|
||||
ellipse(r * sin(i), -r * cos(i),3,3);
|
||||
}
|
||||
|
||||
textAlign(CENTER,CENTER);
|
||||
|
||||
float x=0, y=-r*1.2, nx, ny;
|
||||
for(float i=0; i<=5; i+=0.5) {
|
||||
nx = x*cos(i) - y*sin(i);
|
||||
ny = x*sin(i) + y*cos(i);
|
||||
text("t="+i, nx, ny);
|
||||
}
|
||||
|
||||
fill(150);
|
||||
text("0,0",-10,10);
|
||||
text("1",r+10,15);
|
||||
text("-1",-10,r+10);
|
||||
text("-1",-r-10,15);
|
||||
text("1",-10,-r-10);
|
||||
popStyle();
|
||||
}
|
||||
*/
|
231
components/sections/explanation/index.js
Normal file
231
components/sections/explanation/index.js
Normal file
@@ -0,0 +1,231 @@
|
||||
var React = require("react");
|
||||
var Graphic = require("../../Graphic.jsx");
|
||||
var SectionHeader = require("../../SectionHeader.jsx");
|
||||
var LaTeX = require("../../LaTeX.jsx");
|
||||
|
||||
var Explanation = React.createClass({
|
||||
|
||||
setup: function(api) {
|
||||
|
||||
},
|
||||
|
||||
draw: function(api, curve) {
|
||||
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<section>
|
||||
<SectionHeader {...this.props}>The basics of Bézier curves?</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
|
||||
from any number of inputs to a <strong>single</strong> output. Numbers go in, a single
|
||||
number comes out. Change the numbers that go in, and the number that comes out is still
|
||||
a single number. Parametric functions cheat. They basically say "alright, well, we want
|
||||
multiple values coming out, so we'll just use more than one function". An illustration:
|
||||
Let's say we have a function that maps some value, let's call it <i>x</i>, to
|
||||
some other value, using some kind of number manipulation:</p>
|
||||
|
||||
<LaTeX>\[
|
||||
f(x) = \sin(x)
|
||||
\]</LaTeX>
|
||||
|
||||
<p>The notation <i>f(x)</i> is the standard way to show that it's a function (by convention
|
||||
called <i>f</i> if we're only listing one) and its output changes based on one variable
|
||||
(in this case, <i>x</i>). Change <i>x</i>, and the output for <i>f(x)</i> changes.</p>
|
||||
|
||||
<p>So far so good. Now, let's look at parametric functions, and how they cheat.
|
||||
Let's take the following two functions:</p>
|
||||
|
||||
<LaTeX>\[\begin{matrix}
|
||||
f(a) = \sin(a) \\
|
||||
f(b) = \cos(b)
|
||||
\end{matrix}\]</LaTeX>
|
||||
|
||||
<p>There's nothing really remarkable about them, they're just a sine and cosine function,
|
||||
but you'll notice the inputs have different names. If we change the value for <i>a</i>,
|
||||
we're not going to change the output value for <i>f(b)</i>, since <i>a</i> isn't used
|
||||
in that function. Parametric functions cheat by changing that. In a parametric function
|
||||
all the different functions share a variable, like this:</p>
|
||||
|
||||
<LaTeX>\[
|
||||
\left \{ \begin{matrix}
|
||||
f_a(t) = \sin(t) \\
|
||||
f_b(t) = \cos(t)
|
||||
\end{matrix} \right. \]</LaTeX>
|
||||
|
||||
<p>Multiple functions, but only one variable. If we change the value for <i>t</i>,
|
||||
we change the outcome of both <i>f<sub>a</sub>(t)</i> and <i>f<sub>b</sub>(t)</i>.
|
||||
You might wonder how that's useful, and the answer is actually pretty simple: if
|
||||
we change the labels <i>f<sub>a</sub>(t)</i> and <i>f<sub>b</sub>(t)</i> with what
|
||||
we usually mean with them for parametric curves, things might be a lot more obvious:</p>
|
||||
|
||||
<LaTeX>\[
|
||||
\left \{ \begin{matrix}
|
||||
x = \sin(t) \\
|
||||
y = \cos(t)
|
||||
\end{matrix} \right. \]</LaTeX>
|
||||
|
||||
<p>There we go. <i>x</i>/<i>y</i> coordinates, linked through some mystery value <i>t</i>.</p>
|
||||
|
||||
<p>So, parametric curves don't define a <i>y</i> coordinate in terms of an <i>x</i> coordinate,
|
||||
like normal functions do, but they instead link the values to a "control" variable.
|
||||
If we vary the value of <i>t</i>, then with every change we get <strong>two</strong> values,
|
||||
which we can use as (<i>x</i>,<i>y</i>) coordinates in a graph. The above set of functions,
|
||||
for instance, generates points on a circle: We can range <i>t</i> from negative to positive
|
||||
infinity, and the resulting (<i>x</i>,<i>y</i>) coordinates will always lie on a circle with
|
||||
radius 1 around the origin (0,0). If we plot it for <i>t</i> from 0 to 5, we get this:</p>
|
||||
|
||||
<Graphic preset="empty" title="A (partial) circle: x=sin(t), y=cos(t)" setup={this.setup} draw={this.draw}/>
|
||||
|
||||
<p>Bézier curves are (one in many classes of) parametric functions, and are characterised
|
||||
by using the same base function for all its dimensions. Unlike the above example,
|
||||
where the <i>x</i> and <i>y</i> values use different functions (one uses a sine, the other
|
||||
a cosine), Bézier curves use the "binomial polynomial" for both <i>x</i> and <i>y</i>.
|
||||
So what are binomial polynomials?</p>
|
||||
|
||||
<p>You may remember polynomials from high school, where they're those sums that look like:</p>
|
||||
|
||||
<LaTeX>\[
|
||||
f(x) = a \cdot x^3 + b \cdot x^2 + c \cdot x + d
|
||||
\]</LaTeX>
|
||||
|
||||
<p>If they have a highest order term <i>x³</i> they're called "cubic" polynomials, if it's
|
||||
<i>x²</i> it's a "square" polynomial, if it's just <i>x</i> it's a line (and if there aren't
|
||||
even any terms with <i>x</i> it's not a polynomial!)</p>
|
||||
|
||||
<p>Bézier curves are polynomials of <i>t</i>, rather than <i>x</i>, with the value for <i>t</i>
|
||||
fixed being between 0 and 1, with coefficients <i>a</i>, <i>b</i> etc. taking the "binomial"
|
||||
form, which sounds fancy but is actually a pretty simple description for mixing values:</p>
|
||||
|
||||
<LaTeX>\[ \begin{align*}
|
||||
linear &= (1-t) + t \\
|
||||
square &= (1-t)^2 + 2 \cdot (1-t) \cdot t + t^2 \\
|
||||
cubic &= (1-t)^3 + 3 \cdot (1-t)^2 \cdot t + 3 \cdot (1-t) \cdot t^2 + t^3
|
||||
\end{align*} \]</LaTeX>
|
||||
|
||||
<p>I know what you're thinking: that doesn't look too simple, but if we remove <i>t</i> and
|
||||
add in "times one", things suddenly look pretty easy. Check out these binomial terms:</p>
|
||||
|
||||
<LaTeX>\[ \begin{align*}
|
||||
linear &= \hskip{2.5em} 1 + 1 \\
|
||||
square &= \hskip{1.7em} 1 + 2 + 1\\
|
||||
cubic &= \hskip{0.85em} 1 + 3 + 3 + 1\\
|
||||
hypercubic &= 1 + 4 + 6 + 4 + 1
|
||||
\end{align*} \]</LaTeX>
|
||||
|
||||
<p>Notice that 2 is the same as 1+1, and 3 is 2+1 and 1+2, and 6 is 3+3... As you
|
||||
can see, each time we go up a dimension, we simply start and end with 1, and everything
|
||||
in between is just "the two numbers above it, added together". Now <i>that's</i> easy
|
||||
to remember.</p>
|
||||
|
||||
<p>There's an equally simple way to figure out how the polynomial terms work:
|
||||
if we rename <i>(1-t)</i> to <i>a</i> and <i>t</i> to <i>b</i>, and remove the weights
|
||||
for a moment, we get this:</p>
|
||||
|
||||
<LaTeX>\[ \begin{align*}
|
||||
linear &= BLUE[a] + RED[b] \\
|
||||
square &= BLUE[a] \cdot BLUE[a] + BLUE[a] \cdot RED[b] + RED[b] \cdot RED[b] \\
|
||||
cubic &= BLUE[a] \cdot BLUE[a] \cdot BLUE[a] + BLUE[a] \cdot BLUE[a] \cdot RED[b] + BLUE[a] \cdot RED[b] \cdot RED[b] + RED[b] \cdot RED[b] \cdot RED[b]\\
|
||||
\end{align*} \]</LaTeX>
|
||||
|
||||
<p>It's basically just a sum of "every combination of <i>a</i> and <i>b</i>", progressively
|
||||
replacing <i>a</i>'s with <i>b</i>'s after every + sign. So that's actually pretty simple
|
||||
too. So now you know binomial polynomials, and just for completeness I'm going to show
|
||||
you the generic function for this:</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}}}
|
||||
\]</LaTeX>
|
||||
|
||||
<p>And that's the full description for Bézier curves. Σ in this function indicates that this is
|
||||
a series of additions (using the variable listed below the Σ, starting at ...=<value> and ending
|
||||
at the value listed on top of the Σ).</p>
|
||||
|
||||
<div className="howtocode">
|
||||
<h3>How to implement the basis function</h3>
|
||||
|
||||
<p>We could naively implement the basis function as a mathematical construct,
|
||||
using the function as our guide, like this:</p>
|
||||
|
||||
<pre>function Bezier(n,t):
|
||||
sum = 0
|
||||
for(k=0; k<n; k++):
|
||||
sum += n!/(k!*(n-k)!) * (1-t)^(n-k) * t^(k)
|
||||
return sum</pre>
|
||||
|
||||
<p>I say we could, because we're not going to: the factorial function is <em>incredibly</em>
|
||||
expensive. And, as we can see from the above explanation, we can actually create Pascal's
|
||||
triangle quite easily without it: just start at [1], then [1,1], then [1,2,1], then [1,3,3,1],
|
||||
and so on, with each next row fitting 1 more number than the previous row, starting and
|
||||
ending with "1", with all the numbers in between being the sum of the previous row's
|
||||
elements on either side "above" the one we're computing.</p>
|
||||
|
||||
<p>We can generate this as a list of lists lightning fast, and then never have to compute
|
||||
the binomial terms because we have a lookup table:</p>
|
||||
|
||||
<pre>lut = [ [1], // n=0
|
||||
[1,1], // n=1
|
||||
[1,2,1], // n=2
|
||||
[1,3,3,1], // n=3
|
||||
[1,4,6,4,1], // n=4
|
||||
[1,5,10,10,5,1], // n=5
|
||||
[1,6,15,20,15,6,1]] // n=6
|
||||
|
||||
binomial(n,k):
|
||||
while(n >= lut.length):
|
||||
s = lut.length
|
||||
nextRow = new array(size=s+1)
|
||||
nextRow[0] = 1
|
||||
for(i=1, prev=s-1; i<prev; i++):
|
||||
nextRow[i] = lut[prev][i-1] + lut[prev][i]
|
||||
nextRow[s] = 1
|
||||
lut.add(nextRow)
|
||||
return lut[n][k]</pre>
|
||||
|
||||
<p>So what's going on here? First, we declare a lookup table with a size that's reasonably
|
||||
large enough to accommodate most lookups. Then, we declare a function to get us the values
|
||||
we need, and we make sure that if an n/k pair is requested that isn't in the LUT yet, we
|
||||
expand it first. Our basis function now looks like this:</p>
|
||||
|
||||
<pre>function Bezier(n,t):
|
||||
sum = 0
|
||||
for(k=0; k<n; k++):
|
||||
sum += binomial(n,k) * (1-t)^(n-k) * t^(k)
|
||||
return sum</pre>
|
||||
|
||||
<p>Perfect. Of course, we can optimize further. For most computer graphics purposes, we
|
||||
don't need arbitrary curves. We need quadratic and cubic curves (this primer actually
|
||||
does do arbitrary curves, so you'll find code similar to shown here), which means we can
|
||||
drastically simplify the code:</p>
|
||||
|
||||
<pre>function Bezier(2,t):
|
||||
t2 = t * t
|
||||
mt = 1-t
|
||||
mt2 = mt * mt
|
||||
return mt2 + 2*mt*t + t2
|
||||
|
||||
function Bezier(3,t):
|
||||
t2 = t * t
|
||||
t3 = t2 * t
|
||||
mt = 1-t
|
||||
mt2 = mt * mt
|
||||
mt3 = mt2 * mt
|
||||
return mt3 + 3*mt2*t + 3*mt*t2 + t3</pre>
|
||||
|
||||
<p>And now we know how to program the basis function. Exellent.</p>
|
||||
</div>
|
||||
|
||||
<p>So, now we know what the base function(s) look(s) like, time to add in the magic that makes
|
||||
Bézier curves so special: control points.</p>
|
||||
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Explanation;
|
55
components/sections/index.js
Normal file
55
components/sections/index.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* This is an ordered list of all sections for the article
|
||||
* @type {Object}
|
||||
*/
|
||||
module.exports = {
|
||||
preface: require("./preface"),
|
||||
introduction: require("./introduction"),
|
||||
whatis: require("./whatis"),
|
||||
explanation: require("./explanation")
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
control: require("./control.jsx"),
|
||||
|
||||
matrix: require("./matrix.jsx"),
|
||||
decasteljau: require("./decasteljau.jsx"),
|
||||
flattening: require("./flattening.jsx"),
|
||||
splitting: require("./splitting.jsx"),
|
||||
matrixsplit: require("./matrixsplit.jsx"),
|
||||
reordering: require("./reordering.jsx"),
|
||||
|
||||
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"),
|
||||
|
||||
arclength: require("./arclength.jsx"),
|
||||
arclengthapprox: require("./arclengthapprox.jsx"),
|
||||
tracing: require("./tracing.jsx"),
|
||||
|
||||
intersections: require("./intersections.jsx"),
|
||||
curveintersection: require("./curveintersection.jsx"),
|
||||
moulding: require("./moulding.jsx"),
|
||||
pointcurves: require("./pointcurves.jsx"),
|
||||
|
||||
catmullconv: require("./catmullconv.jsx"),
|
||||
catmullmoulding: require("./catmullmoulding.jsx"),
|
||||
|
||||
polybezier: require("./polybezier.jsx"),
|
||||
shapes: require("./shapes.jsx"),
|
||||
|
||||
projections: require("./projections.jsx"),
|
||||
|
||||
offsetting: require("./offsetting.jsx"),
|
||||
graduatedoffset: require("./graduatedoffset.jsx"),
|
||||
|
||||
circles: require("./circles.jsx"),
|
||||
circles_cubic: require("./circles_cubic.jsx"),
|
||||
arcapproximation: require("./arcapproximation.jsx")
|
||||
*/
|
48
components/sections/introduction/index.js
Normal file
48
components/sections/introduction/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
var React = require("react");
|
||||
var Graphic = require("../../Graphic.jsx");
|
||||
var SectionHeader = require("../../SectionHeader.jsx");
|
||||
|
||||
|
||||
var Introduction = React.createClass({
|
||||
drawQuadratic: function(api) {
|
||||
var curve = api.getDefaultQuadratic();
|
||||
api.setCurve(curve);
|
||||
},
|
||||
|
||||
drawCubic: function(api) {
|
||||
var curve = api.getDefaultCubic();
|
||||
api.setCurve(curve);
|
||||
},
|
||||
|
||||
drawCurve: function(api, curve) {
|
||||
api.reset();
|
||||
api.drawSkeleton(curve);
|
||||
api.drawCurve(curve);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<section>
|
||||
<SectionHeader {...this.props}>A lightning introduction</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,
|
||||
with their curvature influenced by one or more "intermediate" control points. Now, because all the
|
||||
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 }/>
|
||||
|
||||
<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,
|
||||
the Gimp, etc. and in graphic technologies like scalable vector graphics (SVG) and OpenType fonts
|
||||
(ttf/otf). A lot of things use Bézier curves, so if you want to learn more about them... prepare
|
||||
to get your learn on!</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Introduction;
|
43
components/sections/order.list
Normal file
43
components/sections/order.list
Normal file
@@ -0,0 +1,43 @@
|
||||
whatis: require("./whatis.jsx")
|
||||
explanation: require("./explanation.jsx"),
|
||||
control: require("./control.jsx"),
|
||||
|
||||
matrix: require("./matrix.jsx"),
|
||||
decasteljau: require("./decasteljau.jsx"),
|
||||
flattening: require("./flattening.jsx"),
|
||||
splitting: require("./splitting.jsx"),
|
||||
matrixsplit: require("./matrixsplit.jsx"),
|
||||
reordering: require("./reordering.jsx"),
|
||||
|
||||
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"),
|
||||
|
||||
arclength: require("./arclength.jsx"),
|
||||
arclengthapprox: require("./arclengthapprox.jsx"),
|
||||
tracing: require("./tracing.jsx"),
|
||||
|
||||
intersections: require("./intersections.jsx"),
|
||||
curveintersection: require("./curveintersection.jsx"),
|
||||
moulding: require("./moulding.jsx"),
|
||||
pointcurves: require("./pointcurves.jsx"),
|
||||
|
||||
catmullconv: require("./catmullconv.jsx"),
|
||||
catmullmoulding: require("./catmullmoulding.jsx"),
|
||||
|
||||
polybezier: require("./polybezier.jsx"),
|
||||
shapes: require("./shapes.jsx"),
|
||||
|
||||
projections: require("./projections.jsx"),
|
||||
|
||||
offsetting: require("./offsetting.jsx"),
|
||||
graduatedoffset: require("./graduatedoffset.jsx"),
|
||||
|
||||
circles: require("./circles.jsx"),
|
||||
circles_cubic: require("./circles_cubic.jsx"),
|
||||
arcapproximation: require("./arcapproximation.jsx")
|
79
components/sections/preface/index.js
Normal file
79
components/sections/preface/index.js
Normal file
@@ -0,0 +1,79 @@
|
||||
var React = require("react");
|
||||
|
||||
var Preface = React.createClass({
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<section>
|
||||
|
||||
<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
|
||||
are easy to make a computer draw. Give a computer the first and last point in the line, and
|
||||
BAM! straight line. No questions asked.</p>
|
||||
|
||||
<p>Curves, however, are a much bigger problem. While we can draw curves with ridiculous ease
|
||||
freehand, computers are a bit handicapped in that they can't draw curves unless there is a
|
||||
mathematical function that describes how it should be drawn. In fact, they even need this for
|
||||
straight lines, but the function is ridiculously easy, so we tend to ignore that as far as
|
||||
computers are concerned, all lines are "functions", regardless of whether they're straight
|
||||
or curves. However, that does mean that we need to come up with fast-to-compute functions that
|
||||
lead to nice looking curves on a computer. There's a number of these, and in this article
|
||||
we'll focus on a particular function that has received quite a bit of attention, and is used
|
||||
in pretty much anything that can draw curves: "Bézier" curves</p>
|
||||
|
||||
<p>They're named after <a href="https://en.wikipedia.org/wiki/Pierre_B%C3%A9zier">Pierre
|
||||
Bézier</a>, who is principally responsible for getting them known to the world as a curve
|
||||
well-suited for design work (working for Renault and publishing his investigations in 1962),
|
||||
although he was not the first, or only one, to "invent" these type of curves.
|
||||
One might be tempted to say that the mathematician <a href="https://en.wikipedia.org/wiki/Paul_de_Casteljau">Paul
|
||||
de Casteljau</a> was first, investigating the nature of these curves in 1959 while working at
|
||||
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>,
|
||||
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
|
||||
"paths" or worked with vector drawing programs like Flash, Illustrator or Inkscape, those curves
|
||||
you've been drawing are Bézier curves.</p>
|
||||
|
||||
<p>So, what if you need to program them yourself? What are the pitfalls? How do you draw them?
|
||||
What are the bounding boxes, how do you determine intersections, how can you extrude a curve,
|
||||
in short: how do you do everything that you might want when you do with these curves? That's
|
||||
what this page is for. Prepare to be mathed.</p>
|
||||
|
||||
<div>
|
||||
<h2>All Bézier graphics are interactive.</h2>
|
||||
|
||||
<p>This page uses interactive examples, as well as "real" maths (in LaTeX form) which
|
||||
is typeset using the most excellent <a href="http://MathJax.org">MathJax</a> library.
|
||||
All the examples also have a "view source" option, which lets you see how things were
|
||||
implemented using the Bezier.js library.</p>
|
||||
|
||||
<h2>How complicated is the maths going to be?</h2>
|
||||
|
||||
<p>Most of the mathematics in this Primer are early high school maths. If you understand basic
|
||||
arithmetic, and you know how to read English, you should be able to get by just fine. There
|
||||
will at times be <em>far</em> more complicated maths, but if you don't feel like digesting
|
||||
them, you can safely skip over them by either skipping over the "detail boxes" in section
|
||||
or by just jumping to the end of a section with maths that looks too involving. The end of
|
||||
sections typically simply list the conclusions so you can just work with those values directly.</p>
|
||||
|
||||
<h2>Questions, comments:</h2>
|
||||
|
||||
If you have suggestions for new sections, hit up the <a href="https://github.com/pomax/bezierinfo/issues">github
|
||||
issue tracker</a> (also reachable from the repo linked to in the upper right). If you have
|
||||
questions about the material, there's currently no comment section while I'm doing the rewrite,
|
||||
but you can use the issue tracker for that as well. Once the rewrite is done, I'll add a general
|
||||
comment section back in, and maybe a more topical "select this section of text and hit the
|
||||
'question' button to ask a question about it" system. We'll see.
|
||||
|
||||
<p>—Pomax (or in the tweetworld, <a href="https://twitter.com/TheRealPomax">@TheRealPomax</a>)</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Preface;
|
139
components/sections/whatis/index.js
Normal file
139
components/sections/whatis/index.js
Normal file
@@ -0,0 +1,139 @@
|
||||
var React = require("react");
|
||||
var Graphic = require("../../Graphic.jsx");
|
||||
var SectionHeader = require("../../SectionHeader.jsx");
|
||||
var LaTeX = require("../../LaTeX.jsx");
|
||||
|
||||
var Whatis = React.createClass({
|
||||
|
||||
setup: function(api) {
|
||||
this.offset = 20;
|
||||
var curve = api.getDefaultQuadratic();
|
||||
api.setPanelCount(3);
|
||||
api.setCurve(curve);
|
||||
this.dim = api.getPanelWidth();
|
||||
},
|
||||
|
||||
draw: function(api, curve) {
|
||||
var pts = curve.points;
|
||||
var p1 = pts[0], p2=pts[1], p3 = pts[2];
|
||||
var p1e = {
|
||||
x: p1.x + 0.2 * (p2.x - p1.x),
|
||||
y: p1.y + 0.2 * (p2.y - p1.y)
|
||||
};
|
||||
|
||||
var p2e = {
|
||||
x: p2.x + 0.2 * (p3.x - p2.x),
|
||||
y: p2.y + 0.2 * (p3.y - p2.y)
|
||||
};
|
||||
|
||||
var m = {
|
||||
x: p1e.x + 0.2 * (p2e.x - p1e.x),
|
||||
y: p1e.y + 0.2 * (p2e.y - p1e.y)
|
||||
}
|
||||
|
||||
api.reset();
|
||||
|
||||
api.setColor("black");
|
||||
api.setFill("black");
|
||||
api.drawSkeleton(curve);
|
||||
api.drawCurve(curve);
|
||||
|
||||
// draw 20% off-start points and struts
|
||||
api.setColor("blue");
|
||||
api.setWeight(2);
|
||||
|
||||
api.drawLine(p1, p1e);
|
||||
api.drawLine(p2, p2e);
|
||||
api.drawCircle(p1e,3);
|
||||
api.drawCircle(p2e,3);
|
||||
|
||||
api.drawText("linear interpolation distance: " + this.offset + "%", {x:5, y:15});
|
||||
api.drawText("linear interpolation between the first set of points", {x:5, y:this.dim-5});
|
||||
|
||||
// next panel
|
||||
api.setColor("black");
|
||||
api.setWeight(1);
|
||||
api.setOffset({x:this.dim, y:0});
|
||||
api.drawLine({x:0, y:0}, {x:0, y:this.dim});
|
||||
|
||||
api.drawSkeleton(curve);
|
||||
api.drawCurve(curve);
|
||||
|
||||
api.setColor("lightgrey");
|
||||
api.drawLine(p1e, p2e);
|
||||
api.drawCircle(p1e,3);
|
||||
api.drawCircle(p2e,3);
|
||||
|
||||
api.setColor("blue");
|
||||
api.setWeight(2);
|
||||
api.drawLine(p1e, m);
|
||||
api.drawCircle(m,3);
|
||||
|
||||
api.drawText("same linear interpolation distance: " + this.offset + "%", {x:5, y:15});
|
||||
api.drawText("linear interpolation between the second set of points", {x:5, y:this.dim-5});
|
||||
|
||||
// next panel
|
||||
api.setColor("black");
|
||||
api.setWeight(1);
|
||||
api.setOffset({x:2*this.dim, y:0});
|
||||
api.drawLine({x:0, y:0}, {x:0, y:this.dim});
|
||||
|
||||
api.drawSkeleton(curve);
|
||||
api.drawCurve(curve);
|
||||
api.drawCircle(m,3);
|
||||
|
||||
api.drawText("the second interpolation turns out to be a curve point!", {x:5, y:this.dim-5});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<section>
|
||||
<SectionHeader {...this.props}>What is a Bézier Curve?</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>
|
||||
|
||||
<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>
|
||||
|
||||
<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
|
||||
\]</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>
|
||||
|
||||
<Graphic preset="threepanel" 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>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>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Whatis;
|
Reference in New Issue
Block a user