regenerated all images
@@ -24,7 +24,7 @@ draw() {
|
||||
end();
|
||||
|
||||
setFill(`black`);
|
||||
text(`Approximating with ${this.steps} strips: ${computedArea} (true area: ${trueArea}`, w/2, h-10, CENTER);
|
||||
text(`Approximating with ${this.steps} strips : ${computedArea} (true area: ${trueArea})`, w/2, h-10, CENTER);
|
||||
}
|
||||
|
||||
drawSlices(w, h, a, n) {
|
||||
|
@@ -7,11 +7,11 @@ If we combine the work done in the previous sections on curve flattening and arc
|
||||
<div class="figure">
|
||||
|
||||
<graphics-element title="Approximate quadratic curve arc length" src="./approximate.js" data-type="quadratic">
|
||||
<input type="range" min="1" max="24" step="1" value="4" class="slide-control">
|
||||
<input type="range" min="2" max="24" step="1" value="4" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="Approximate cubic curve arc length" src="./approximate.js" data-type="cubic">
|
||||
<input type="range" min="1" max="32" step="1" value="8" class="slide-control">
|
||||
<input type="range" min="2" max="32" step="1" value="8" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
</div>
|
||||
|
@@ -19,7 +19,7 @@ draw() {
|
||||
setStroke(`lightgrey`);
|
||||
drawGrid(20);
|
||||
|
||||
setStroke(`#CC00CC99`);
|
||||
setStroke(this.parameters.showCurves ? `transparent` : `#CC00CC99`);
|
||||
for (let i=0, e=points.length-1, p, n; i<e; i++) {
|
||||
p = points[i];
|
||||
n = points[i+1];
|
||||
@@ -27,7 +27,10 @@ draw() {
|
||||
}
|
||||
|
||||
setColor(`black`);
|
||||
points.forEach(p => circle(p.x, p.y, 3));
|
||||
points.forEach((p,i) => {
|
||||
circle(p.x, p.y, 3)
|
||||
text(`${i+1}`, p.x+5, p.y+5);
|
||||
});
|
||||
|
||||
this.drawSplineData();
|
||||
}
|
||||
@@ -36,10 +39,17 @@ drawSplineData() {
|
||||
// we'll need at least 4 points
|
||||
if (points.length <= 3) return;
|
||||
|
||||
if (this.parameters.showCurves) {
|
||||
for(let i=0; i<points.length-3; i++) {
|
||||
let c = new Bezier(this, points.slice(i,i+4));
|
||||
c.drawCurve(randomColor());
|
||||
}
|
||||
}
|
||||
|
||||
let spline = new BSpline(this, points);
|
||||
|
||||
noFill();
|
||||
setStroke(`black`);
|
||||
setStroke(this.parameters.showCurves ? `#00000040` : `black`);
|
||||
start();
|
||||
spline.getLUT((points.length - 3) * 20).forEach(p => vertex(p.x, p.y));
|
||||
end();
|
||||
|
@@ -15,6 +15,10 @@ Consider the difference to be this:
|
||||
- for Bézier curves, the curve is defined as an interpolation of points, but:
|
||||
- for B-Splines, the curve is defined as an interpolation of *curves*.
|
||||
|
||||
In fact, let's look at that again, but this time with the base curves shown, too. Each consecutive four points defined one curve:
|
||||
|
||||
<graphics-element title="The components of a B-Spline " width="600" height="300" src="./basic.js" data-show-curves="true"></graphics-element>
|
||||
|
||||
In order to make this interpolation of curves work, the maths is necessarily more complex than the maths for Bézier curves, so let's have a look at how things work.
|
||||
|
||||
|
||||
|
@@ -31,7 +31,7 @@ setup() {
|
||||
knots = new BSpline(this, points).formKnots();
|
||||
let min = 0, max = knots.length-1;
|
||||
knots.forEach((_,i) => {
|
||||
addSlider(`slide-control`, false, min, max, 0.01, i, (v) => this.setKnotValue(i,v));
|
||||
addSlider(`slide-control`, `!knot ${i+1}`, min, max, 0.01, i, (v) => this.setKnotValue(i,v));
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -12,7 +12,7 @@ setup() {
|
||||
weights = new BSpline(this, points, !!this.parameters.open).formWeights();
|
||||
|
||||
points.forEach((_,i) => {
|
||||
addSlider(`slide-control`, false, 0, 10, 0.1, i%2===1? 2 : 6, v => this.setWeight(i, v));
|
||||
addSlider(`slide-control`, `!weight ${i+1}`, 0, 10, 0.1, i%2===1? 2 : 6, v => this.setWeight(i, v));
|
||||
});
|
||||
|
||||
points = points.concat(points.slice(0,3));
|
||||
|
@@ -20,7 +20,7 @@ setup() {
|
||||
|
||||
let min=0, max=knots.length-1;
|
||||
knots.forEach((_,i) => {
|
||||
addSlider(`slide-control`, false, min, max, 0.01, knots[i], v => this.setKnotValue(i, v));
|
||||
addSlider(`slide-control`, `!knot ${i+1}`, min, max, 0.01, knots[i], v => this.setKnotValue(i, v));
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -12,7 +12,7 @@ setup() {
|
||||
knots = new BSpline(this, points).formKnots(!!this.parameters.open);
|
||||
let min=0, max=knots.length-1;
|
||||
knots.forEach((_,i) => {
|
||||
addSlider(`slide-control`, false, min, max, 0.01, knots[i], v => this.setKnotValue(i, v));
|
||||
addSlider(`slide-control`, `!knot ${i+1}`, min, max, 0.01, knots[i], v => this.setKnotValue(i, v));
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -21,6 +21,8 @@ transformTension(v) {
|
||||
|
||||
draw() {
|
||||
clear();
|
||||
setStroke(`lightgrey`);
|
||||
drawGrid(20);
|
||||
|
||||
const p = points, n = points.length;
|
||||
for (let i=1, e=n-2; i<e; i++) {
|
||||
|
@@ -255,7 +255,7 @@ So let's try it out! The following graphic lets you place points, and will start
|
||||
|
||||
<graphics-element title="Fitting a Bézier curve" width="550" src="./curve-fitting.js" >
|
||||
<button class="toggle">toggle</button>
|
||||
<div class="sliders"><!-- this will contain range inputs, created by the graphic --></div>
|
||||
<!-- additional sliders will get created on the fly -->
|
||||
</graphics-element>
|
||||
|
||||
You'll note there is a convenient "toggle" buttons that lets you toggle between equidistant `t` values, and distance ratio along the polygon formed by the points. Arguably more interesting is that once you have points to abstract a curve, you also get <em>direct control</em> over the time values through sliders for each, because if the time values are our degree of freedom, you should be able to freely manipulate them and see what the effect on your curve is.
|
||||
|
@@ -110,7 +110,7 @@ updateSliders() {
|
||||
const l = points.length-1;
|
||||
if (l >= 2) {
|
||||
points.forEach((_,i) => {
|
||||
addSlider(`slide-control`, false, 0, 1, 0.01, i/l, v => this.setTvalue(i, v));
|
||||
addSlider(`slide-control`, `!t<sub>${i}</sub>`, 0, 1, 0.01, i/l, v => this.setTvalue(i, v));
|
||||
});
|
||||
}
|
||||
this.label = `Using equidistant t values`;
|
||||
|
@@ -17,9 +17,9 @@ The following graphic applies this algorithm to a pair of cubic curves, one step
|
||||
(can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)
|
||||
|
||||
<graphics-element title="Curve/curve intersections" width="825" src="./curve-curve.js">
|
||||
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
|
||||
<button class="next">Advance one step</button>
|
||||
<button class="reset">Reset</button>
|
||||
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
Finding self-intersections is effectively the same procedure, except that we're starting with a single curve, so we need to turn that into two separate curves first. This is trivially achieved by splitting at an inflection point, or if there are none, just splitting at `t=0.5` first, and then running the exact same algorithm as above, with all non-overlapping curve pairs getting removed at each iteration, and each successive step homing in on the curve's self-intersection points.
|
||||
|
@@ -4,7 +4,7 @@ setup() {
|
||||
setPanelCount(3);
|
||||
this.pairReset();
|
||||
this.setupEventListening();
|
||||
setSlider(`.slide-control`, `epsilon`, 1.0, v => this.reset(v));
|
||||
setSlider(`.slide-control`, `threshold`, 1.0, v => this.reset(v));
|
||||
}
|
||||
|
||||
pairReset() {
|
||||
@@ -52,7 +52,7 @@ draw() {
|
||||
|
||||
this.drawIteration();
|
||||
setFill(`black`);
|
||||
let information = `Initial curves, threshold = ${this.epsilon}px`
|
||||
let information = `Initial curves, threshold = ${this.threshold}px`
|
||||
if (this.step) {
|
||||
information = `Curve collection at iteration ${this.step}`;
|
||||
}
|
||||
@@ -72,7 +72,7 @@ drawIteration() {
|
||||
const pairs = this.pairs;
|
||||
this.pairs = [];
|
||||
pairs.forEach(pair => {
|
||||
if(pair[0].length() < this.epsilon && pair[1].length() < this.epsilon)
|
||||
if(pair[0].length() < this.threshold && pair[1].length() < this.threshold)
|
||||
return this.finals.push(pair);
|
||||
|
||||
// split two curves into four curves
|
||||
|
@@ -73,6 +73,6 @@ treated as a sequence of three (elementary) shear operations. When we combine th
|
||||
The following two graphics show the tangent and normal along a quadratic and cubic curve, with the direction vector coloured blue, and the normal vector coloured red (the markers are spaced out evenly as *t*-intervals, not spaced equidistant).
|
||||
|
||||
<div class="figure">
|
||||
<graphics-element title="Quadratic Bézier tangents and normals" src="./quadratic.js"></graphics-element>
|
||||
<graphics-element title="Cubic Bézier tangents and normals" src="./cubic.js"></graphics-element>
|
||||
<graphics-element title="Quadratic Bézier tangents and normals" src="./pointvectors.js" data-type="quadratic"></graphics-element>
|
||||
<graphics-element title="Cubic Bézier tangents and normals" src="./pointvectors.js" data-type="cubic"></graphics-element>
|
||||
</div>
|
||||
|
@@ -1,7 +1,16 @@
|
||||
let curve;
|
||||
|
||||
setup() {
|
||||
curve = Bezier.defaultCubic(this);
|
||||
const type = this.type = this.parameters.type ?? `quadratic`;
|
||||
if (type === `quadratic`) {
|
||||
curve = Bezier.defaultQuadratic(this);
|
||||
} else {
|
||||
curve = Bezier.defaultCubic(this);
|
||||
curve.points[0].x = 30;
|
||||
curve.points[0].y = 230;
|
||||
curve.points[1].x = 75;
|
||||
curve.points[1].y = 50;
|
||||
}
|
||||
setMovable(curve.points);
|
||||
}
|
||||
|
||||
@@ -15,7 +24,7 @@ draw() {
|
||||
for(let i=0; i<=10; i++) {
|
||||
let t = i/10.0;
|
||||
let p = curve.get(t);
|
||||
let d = this.getDerivative(t, pts);
|
||||
let d = this.type === `quadratic` ? this.getQuadraticDerivative(t, pts) : this.getCubicDerivative(t, pts);
|
||||
|
||||
let m = sqrt(d.x*d.x + d.y*d.y);
|
||||
d = { x: d.x/m, y: d.y/m };
|
||||
@@ -27,9 +36,6 @@ draw() {
|
||||
setStroke(`red`);
|
||||
line(p.x, p.y, p.x + n.x*f, p.y + n.y*f);
|
||||
|
||||
setStroke(`purple`);
|
||||
line(p.x, p.y, p.x - n.x*f, p.y - n.y*f);
|
||||
|
||||
setStroke(`black`);
|
||||
circle(p.x, p.y, 3);
|
||||
}
|
||||
@@ -37,7 +43,25 @@ draw() {
|
||||
curve.drawPoints();
|
||||
}
|
||||
|
||||
getDerivative(t, points) {
|
||||
getQuadraticDerivative(t, points) {
|
||||
let mt = (1 - t), d = [
|
||||
{
|
||||
x: 2 * (points[1].x - points[0].x),
|
||||
y: 2 * (points[1].y - points[0].y)
|
||||
},
|
||||
{
|
||||
x: 2 * (points[2].x - points[1].x),
|
||||
y: 2 * (points[2].y - points[1].y)
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
x: mt * d[0].x + t * d[1].x,
|
||||
y: mt * d[0].y + t * d[1].y
|
||||
};
|
||||
}
|
||||
|
||||
getCubicDerivative(t, points) {
|
||||
let mt = (1 - t), a = mt*mt, b = mt*t, c = t*t, d = [
|
||||
{
|
||||
x: 3 * (points[1].x - points[0].x),
|
@@ -1,61 +0,0 @@
|
||||
let curve;
|
||||
|
||||
setup() {
|
||||
curve = Bezier.defaultQuadratic(this);
|
||||
setMovable(curve.points);
|
||||
}
|
||||
|
||||
draw() {
|
||||
clear();
|
||||
curve.drawSkeleton();
|
||||
|
||||
const pts = curve.points;
|
||||
const f = 15;
|
||||
|
||||
for(let i=0; i<=10; i++) {
|
||||
let t = i/10.0;
|
||||
let p = curve.get(t);
|
||||
let d = this.getDerivative(t, pts);
|
||||
|
||||
let m = sqrt(d.x*d.x + d.y*d.y);
|
||||
d = { x: d.x/m, y: d.y/m };
|
||||
let n = this.getNormal(t, d);
|
||||
|
||||
setStroke(`blue`);
|
||||
line(p.x, p.y, p.x + d.x*f, p.y + d.y*f);
|
||||
|
||||
setStroke(`red`);
|
||||
line(p.x, p.y, p.x + n.x*f, p.y + n.y*f);
|
||||
|
||||
setStroke(`purple`);
|
||||
line(p.x, p.y, p.x - n.x*f, p.y - n.y*f);
|
||||
|
||||
setStroke(`black`);
|
||||
circle(p.x, p.y, 3);
|
||||
}
|
||||
|
||||
curve.drawPoints();
|
||||
}
|
||||
|
||||
getDerivative(t, points) {
|
||||
let mt = (1 - t), d = [
|
||||
{
|
||||
x: 2 * (points[1].x - points[0].x),
|
||||
y: 2 * (points[1].y - points[0].y)
|
||||
},
|
||||
{
|
||||
x: 2 * (points[2].x - points[1].x),
|
||||
y: 2 * (points[2].y - points[1].y)
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
x: mt * d[0].x + t * d[1].x,
|
||||
y: mt * d[0].y + t * d[1].y
|
||||
};
|
||||
}
|
||||
|
||||
getNormal(t, d) {
|
||||
const q = sqrt(d.x * d.x + d.y * d.y);
|
||||
return { x: -d.y / q, y: d.x / q };
|
||||
}
|
@@ -28,7 +28,7 @@ And then we're done, we found "the" normal vector for a 3D curve. Let's see what
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around" around between t=0.5 and t=0.9... Why is doing that?
|
||||
However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around the curve" between t=0.65 and t=0.75... Why is it doing that?
|
||||
|
||||
As it turns out, it's doing that because that's how the maths works, and that's the problem with Frenet normals: while they are "mathematically correct", they are "practically problematic", and so for any kind of graphics work what we really want is a way to compute normals that just... look good.
|
||||
|
||||
|
@@ -76,14 +76,14 @@ movePointsQuadratic(i, link) {
|
||||
let pl = points[(l+i-1)%l];
|
||||
let pr = points[(l+i+1)%l];
|
||||
let ppr = points[(l+i+3)%l];
|
||||
pl.x += this.currentPoint.diff.x;
|
||||
pl.y += this.currentPoint.diff.y;
|
||||
pr.x += this.currentPoint.diff.x;
|
||||
pr.y += this.currentPoint.diff.y;
|
||||
ppl.x -= this.currentPoint.diff.x;
|
||||
ppl.y -= this.currentPoint.diff.y;
|
||||
ppr.x -= this.currentPoint.diff.x;
|
||||
ppr.y -= this.currentPoint.diff.y;
|
||||
pl.x += this.cursor.diff.x;
|
||||
pl.y += this.cursor.diff.y;
|
||||
pr.x += this.cursor.diff.x;
|
||||
pr.y += this.cursor.diff.y;
|
||||
ppl.x -= this.cursor.diff.x;
|
||||
ppl.y -= this.cursor.diff.y;
|
||||
ppr.x -= this.cursor.diff.x;
|
||||
ppr.y -= this.cursor.diff.y;
|
||||
|
||||
this.problem = points[(l+i+4)%l];
|
||||
if (ppr.y === this.problem.y) {
|
||||
@@ -129,11 +129,12 @@ movePointsCubic(i, link) {
|
||||
const l = points.length;
|
||||
if (link === `conventional` && i%3 === 0) {
|
||||
let left = points[(l+i-1)%l];
|
||||
left.x += this.currentPoint.diff.x;
|
||||
left.y += this.currentPoint.diff.y;
|
||||
left.x += this.cursor.diff.x;
|
||||
left.y += this.cursor.diff.y;
|
||||
|
||||
let right = points[(l+i+1)%l];
|
||||
right.x += this.currentPoint.diff.x;
|
||||
right.y += this.currentPoint.diff.y;
|
||||
right.x += this.cursor.diff.x;
|
||||
right.y += this.cursor.diff.y;
|
||||
}
|
||||
|
||||
if (i%3 > 0) {
|
||||
|
@@ -24,4 +24,4 @@ This makes the interval we check smaller and smaller at each iteration, and we c
|
||||
|
||||
So, let's see that in action: in this case, I'm going to arbitrarily say that if we're going to run the loop until the interval is smaller than 0.001, and show you what that means for projecting your mouse cursor or finger tip onto a rather complex Bézier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.
|
||||
|
||||
<graphics-element title="Projecting a point onto a Bézier curve" width="320" height="320" src="./project.js"></graphics-element>
|
||||
<graphics-element title="Projecting a point onto a Bézier curve" width="400" height="400" src="./project.js"></graphics-element>
|
||||
|
@@ -2,16 +2,16 @@ let curve;
|
||||
|
||||
setup() {
|
||||
curve = new Bezier(this, [
|
||||
{x:248,y:188},
|
||||
{x:218,y:294},
|
||||
{x:45,y:290},
|
||||
{x:12,y:236},
|
||||
{x:14,y:82},
|
||||
{x:186,y:177},
|
||||
{x:221,y:90},
|
||||
{x:18,y:156},
|
||||
{x:34,y:57},
|
||||
{x:198,y:18}
|
||||
{x:288,y:218},
|
||||
{x:258,y:334},
|
||||
{x:85,y:330},
|
||||
{x:52,y:276},
|
||||
{x:54,y:122},
|
||||
{x:216,y:217},
|
||||
{x:261,y:130},
|
||||
{x:58,y:196},
|
||||
{x:84,y:97},
|
||||
{x:238,y:58}
|
||||
]);
|
||||
|
||||
this.cursor.x = 280;
|
||||
|
@@ -23,6 +23,8 @@ bindButtons() {
|
||||
|
||||
draw() {
|
||||
clear();
|
||||
setStroke(`lightgrey`);
|
||||
drawGrid(20);
|
||||
this.drawCurve();
|
||||
}
|
||||
|
||||
@@ -31,6 +33,7 @@ drawCurve() {
|
||||
// And the canvas only does 2nd and 3rd - we use de Casteljau's algorithm:
|
||||
start();
|
||||
noFill();
|
||||
setStroke(`black`);
|
||||
for(let t=0; t<=1; t+=0.01) {
|
||||
let q = JSON.parse(JSON.stringify(points));
|
||||
while(q.length > 1) {
|
||||
@@ -113,9 +116,9 @@ lower() {
|
||||
// And then we map our k-order list of coordinates
|
||||
// to an n-order list of coordinates, instead:
|
||||
const V = Mi.multiply(Mt);
|
||||
const x = new Matrix(pts.map(p => [p.x]));
|
||||
const x = new Matrix(points.map(p => [p.x]));
|
||||
const nx = V.multiply(x);
|
||||
const y = new Matrix(pts.map(p => [p.y]));
|
||||
const y = new Matrix(points.map(p => [p.y]));
|
||||
const ny = V.multiply(y);
|
||||
|
||||
points = nx.data.map((x,i) => ({
|
||||
|
@@ -23,10 +23,10 @@ This does something unexpected: it turns our polynomial into something that _isn
|
||||
But the best way to show what this does is to do literally that: let's look at the effect of "rationalising" our Bézier curves using an interactive graphic for a rationalised curves. The following graphic shows the Bézier curve from the previous section, "enriched" with ratio factors for each coordinate. The closer to zero we set one or more terms, the less relative influence the associated coordinate exerts on the curve (and of course the higher we set them, the more influence they have). Try to change the values and see how it affects what gets drawn:
|
||||
|
||||
<graphics-element title="Our rational cubic Bézier curve" src="./rational.js">
|
||||
ratio 1 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
ratio 2 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
ratio 3 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
ratio 4 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span>
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-1">
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-2">
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-3">
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-4">
|
||||
</graphics-element>
|
||||
|
||||
You can think of the ratio values as each coordinate's "gravity": the higher the gravity, the closer to that coordinate the curve will want to be. You'll also notice that if you simply increase or decrease all the ratios by the same amount, nothing changes... much like with gravity, if the relative strengths stay the same, nothing really changes. The values define each coordinate's influence _relative to all other points_.
|
||||
|
@@ -1,24 +1,17 @@
|
||||
let curve;
|
||||
let curve, ratios=[1, 1, 1, 1];
|
||||
|
||||
setup() {
|
||||
curve = Bezier.defaultCubic(this);
|
||||
setMovable(curve.points);
|
||||
curve.points.forEach((p,i) => {
|
||||
setSlider(`.ratio-${i+1}`, `!ratio-${i+1}`, 1, v => this.setRatio(i,v))
|
||||
});
|
||||
}
|
||||
|
||||
const inputs = findAll(`input[type=range]`);
|
||||
if (inputs) {
|
||||
const ratios = inputs.map(i => parseFloat(i.value));
|
||||
curve.setRatios(ratios);
|
||||
|
||||
inputs.forEach((input,pos) => {
|
||||
const span = input.nextSibling;
|
||||
input.listen(`input`, evt => {
|
||||
const value = parseFloat(evt.target.value);
|
||||
span.textContent = ratios[pos] = value;
|
||||
curve.update();
|
||||
this.redraw();
|
||||
});
|
||||
})
|
||||
}
|
||||
setRatio(i, v) {
|
||||
ratios[i] = v;
|
||||
curve.setRatios(ratios);
|
||||
redraw();
|
||||
}
|
||||
|
||||
draw() {
|
||||
|
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 25 KiB |
@@ -412,13 +412,13 @@ function Bezier(3,t,w[]):
|
||||
<graphics-element title="Our rational cubic Bézier curve" width="275" height="275" src="./chapters/weightcontrol/rational.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\weightcontrol\0ef06657f0540938686b9b35b249a22b.png">
|
||||
<img width="275px" height="275px" src="images\chapters\weightcontrol\be18e8119472af796329f3e2159bdf94.png">
|
||||
<label>Our rational cubic Bézier curve</label>
|
||||
</fallback-image>
|
||||
ratio 1 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
ratio 2 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
ratio 3 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
ratio 4 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span>
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-1">
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-2">
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-3">
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-4">
|
||||
</graphics-element>
|
||||
|
||||
<p>You can think of the ratio values as each coordinate's "gravity": the higher the gravity, the closer to that coordinate the curve will want to be. You'll also notice that if you simply increase or decrease all the ratios by the same amount, nothing changes... much like with gravity, if the relative strengths stay the same, nothing really changes. The values define each coordinate's influence <em>relative to all other points</em>.</p>
|
||||
@@ -742,7 +742,7 @@ function drawCurve(points[], t):
|
||||
<graphics-element title="A variable-order Bézier curve" width="275" height="275" src="./chapters/reordering/reorder.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\reordering\387f931043aabd6c467985c568482636.png">
|
||||
<img width="275px" height="275px" src="images\chapters\reordering\81e559a69298ecc24c8edebe59e79647.png">
|
||||
<label>A variable-order Bézier curve</label>
|
||||
</fallback-image>
|
||||
<button class="raise">raise</button>
|
||||
@@ -815,16 +815,16 @@ treated as a sequence of three (elementary) shear operations. When we combine th
|
||||
|
||||
<p>The following two graphics show the tangent and normal along a quadratic and cubic curve, with the direction vector coloured blue, and the normal vector coloured red (the markers are spaced out evenly as <em>t</em>-intervals, not spaced equidistant).</p>
|
||||
<div class="figure">
|
||||
<graphics-element title="Quadratic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/quadratic.js" >
|
||||
<graphics-element title="Quadratic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/pointvectors.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\44d454f380ae84dbe4661bd67c406edc.png">
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\f4d72cc162d4cbcc6d6a459fced01cfb.png">
|
||||
<label>Quadratic Bézier tangents and normals</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Cubic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/cubic.js" >
|
||||
<graphics-element title="Cubic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/pointvectors.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\58719bde12c1cbd535d5d8af1bef9c2b.png">
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\2724b6c42f36b4dbea6962cac844d2c3.png">
|
||||
<label>Cubic Bézier tangents and normals</label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
@@ -860,7 +860,7 @@ treated as a sequence of three (elementary) shear operations. When we combine th
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<p>However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around" around between t=0.5 and t=0.9... Why is doing that?</p>
|
||||
<p>However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around the curve" between t=0.65 and t=0.75... Why is it doing that?</p>
|
||||
<p>As it turns out, it's doing that because that's how the maths works, and that's the problem with Frenet normals: while they are "mathematically correct", they are "practically problematic", and so for any kind of graphics work what we really want is a way to compute normals that just... look good.</p>
|
||||
<p>Thankfully, Frenet normals are not our only option.</p>
|
||||
<p>Another option is to take a slightly more algorithmic approach and compute a form of <a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/12/Computation-of-rotation-minimizing-frames.pdf">Rotation Minimising Frame</a> (also known as "parallel transport frame" or "Bishop frame") instead, where a "frame" is a set made up of the tangent, the rotational axis, and the normal vector, centered on an on-curve point.</p>
|
||||
@@ -1359,19 +1359,19 @@ y = curve.get(t).y</code></pre>
|
||||
<graphics-element title="A function's approximated integral" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="10">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\580c33f599b70de44b17c546098508aa.png">
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\dc74a2f2da19470b8d721ece5f3ce268.png">
|
||||
<label>A function's approximated integral</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="A better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="24">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\0d7138b99f5986332a050a8479eefa57.png">
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\4bffba7dda2a3556cf5b2ae7392083c6.png">
|
||||
<label>A better approximation</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="An even better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="99">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\475547c773a7279dc037c9ced2c8c6dc.png">
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\4b5d220d02b08f6c9aa19389255ef8bb.png">
|
||||
<label>An even better approximation</label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
@@ -1412,7 +1412,7 @@ y = curve.get(t).y</code></pre>
|
||||
<img width="275px" height="275px" src="images\chapters\arclengthapprox\a040f6b7c7c33ada25ecfd1060726545.png">
|
||||
<label>Approximate quadratic curve arc length</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="1" max="24" step="1" value="4" class="slide-control">
|
||||
<input type="range" min="2" max="24" step="1" value="4" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="Approximate cubic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="cubic">
|
||||
@@ -1421,7 +1421,7 @@ y = curve.get(t).y</code></pre>
|
||||
<img width="275px" height="275px" src="images\chapters\arclengthapprox\c270144cc41e4ebc4b0b2331473530fa.png">
|
||||
<label>Approximate cubic curve arc length</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="1" max="32" step="1" value="8" class="slide-control">
|
||||
<input type="range" min="2" max="32" step="1" value="8" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
</div>
|
||||
@@ -1588,12 +1588,12 @@ lli = function(line1, line2):
|
||||
<graphics-element title="Curve/curve intersections" width="825" height="275" src="./chapters/curveintersection/curve-curve.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\curveintersection\9540ecc84c3fc63a07f5372ab031c99e.png">
|
||||
<img width="825px" height="275px" src="images\chapters\curveintersection\390fe022c4e290a7a9d3814ae2936b77.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
|
||||
<button class="next">Advance one step</button>
|
||||
<button class="reset">Reset</button>
|
||||
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<p>Finding self-intersections is effectively the same procedure, except that we're starting with a single curve, so we need to turn that into two separate curves first. This is trivially achieved by splitting at an inflection point, or if there are none, just splitting at <code>t=0.5</code> first, and then running the exact same algorithm as above, with all non-overlapping curve pairs getting removed at each iteration, and each successive step homing in on the curve's self-intersection points.</p>
|
||||
@@ -1724,10 +1724,10 @@ for (coordinate, index) in LUT:
|
||||
</ol>
|
||||
<p>This makes the interval we check smaller and smaller at each iteration, and we can keep running the three steps until the interval becomes so small as to lead to distances that are, for all intents and purposes, the same for all points.</p>
|
||||
<p>So, let's see that in action: in this case, I'm going to arbitrarily say that if we're going to run the loop until the interval is smaller than 0.001, and show you what that means for projecting your mouse cursor or finger tip onto a rather complex Bézier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.</p>
|
||||
<graphics-element title="Projecting a point onto a Bézier curve" width="320" height="320" src="./chapters/projections/project.js" >
|
||||
<graphics-element title="Projecting a point onto a Bézier curve" width="400" height="400" src="./chapters/projections/project.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="320px" height="320px" src="images\chapters\projections\c40ab9e3f3d1f53872dff30a7bcdb003.png">
|
||||
<img width="400px" height="400px" src="images\chapters\projections\3cc334d0ebc01cc5352e23ed47bc5414.png">
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -1847,11 +1847,11 @@ for (coordinate, index) in LUT:
|
||||
<graphics-element title="Fitting a Bézier curve" width="550" height="275" src="./chapters/curvefitting/curve-fitting.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="550px" height="275px" src="images\chapters\curvefitting\78d32beb061391c47217611128446146.png">
|
||||
<img width="550px" height="275px" src="images\chapters\curvefitting\1307361f152242ab0263d81a469cf43b.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<button class="toggle">toggle</button>
|
||||
<div class="sliders"><!-- this will contain range inputs, created by the graphic --></div>
|
||||
<!-- additional sliders will get created on the fly -->
|
||||
</graphics-element>
|
||||
|
||||
<p>You'll note there is a convenient "toggle" buttons that lets you toggle between equidistant <code>t</code> values, and distance ratio along the polygon formed by the points. Arguably more interesting is that once you have points to abstract a curve, you also get <em>direct control</em> over the time values through sliders for each, because if the time values are our degree of freedom, you should be able to freely manipulate them and see what the effect on your curve is.</p>
|
||||
@@ -1864,7 +1864,7 @@ for (coordinate, index) in LUT:
|
||||
<graphics-element title="A Catmull-Rom curve" width="275" height="275" src="./chapters/catmullconv/catmull-rom.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\catmullconv\aa46749b9469341d9249ca452390d875.png">
|
||||
<img width="275px" height="275px" src="images\chapters\catmullconv\e1e640b29dd07f905c1555438511cad9.png">
|
||||
<label>A Catmull-Rom curve</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0.1" max="1" step="0.01" value="0.5" class="slide-control tension">
|
||||
@@ -1992,13 +1992,13 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="Unlinked quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="coordinate">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\0553872bf00ebc557f4b11d69d634fc6.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\044f65dd588b0210499add16ea50a23d.png">
|
||||
<label>Unlinked quadratic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Unlinked cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="coordinate">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\60adbd6fe091a72e1ed37355e49a506e.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\babb27083b805c7c77bb93cfecbefb2b.png">
|
||||
<label>Unlinked cubic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -2010,13 +2010,13 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="Connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="derivative">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\1aeb331a5170c203c31c55ae8d55b809.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\b492c486c25f17a95c690e235b8ad483.png">
|
||||
<label>Connected quadratic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="derivative">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\04bc7b98ba019a4a90bedce6e10eaf08.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\adc3b55c9956849ec86ecffcd8864d8a.png">
|
||||
<label>Connected cubic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -2026,13 +2026,13 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="Angularly connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="direction">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\acb8f004751017eaac4aae0b039c94cf.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\17a6ffbfffaa9046ad165ca880d9030d.png">
|
||||
<label>Angularly connected quadratic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Angularly connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="direction">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\1a3c6b24bea874d32334d62110507fcb.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\b7dfe772ac90d762f48772b691a9070f.png">
|
||||
<label>Angularly connected cubic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -2041,13 +2041,13 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="Standard connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="conventional">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\7afd119dd93a581ec161e2cbfc3c1e63.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\a1a3aabd22c0221beb38403a4532ea86.png">
|
||||
<label>Standard connected quadratic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Standard connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="conventional">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\cfbfbbc56365ba547dc7e82b329c4007.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\b810f02639a79cf7f8ae416d7185614d.png">
|
||||
<label>Standard connected cubic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>>
|
||||
|
||||
@@ -2336,7 +2336,7 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="A B-Spline example" width="600" height="300" src="./chapters/bsplines/basic.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\610232b8f7ce7ef3f0f012d55e385c6d.png">
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\d9a99344a5de4b5be77824f8f8caa707.png">
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -2346,6 +2346,14 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<li>for Bézier curves, the curve is defined as an interpolation of points, but:</li>
|
||||
<li>for B-Splines, the curve is defined as an interpolation of <em>curves</em>.</li>
|
||||
</ul>
|
||||
<p>In fact, let's look at that again, but this time with the base curves shown, too. Each consecutive four points defined one curve:</p>
|
||||
<graphics-element title="The components of a B-Spline " width="600" height="300" src="./chapters/bsplines/basic.js" data-show-curves="true">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\3f66c97dcdd56d201c2acda3bd403bbf.png">
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>In order to make this interpolation of curves work, the maths is necessarily more complex than the maths for Bézier curves, so let's have a look at how things work.</p>
|
||||
<h2>How to compute a B-Spline curve: some maths</h2>
|
||||
<p>Given a B-Spline of degree <code>d</code> and thus order <code>k=d+1</code> (so a quadratic B-Spline is degree 2 and order 3, a cubic B-Spline is degree 3 and order 4, etc) and <code>n</code> control points <code>P<sub>0</sub></code> through <code>P<sub>n-1</sub></code>, we can compute a point on the curve for some value <code>t</code> in the interval [0,1] (where 0 is the start of the curve, and 1 the end, just like for Bézier curves), by evaluating the following function:</p>
|
||||
@@ -2379,7 +2387,7 @@ Doing so for a degree <code>d</code> B-Spline with <code>n</code> control point
|
||||
<graphics-element title="Visualising relative interpolation strengths" width="600" height="300" src="./chapters/bsplines/interpolation.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\84bd623b9d3d8bf01a656f49c17079b8.png">
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\bd03680e4661ae7f4d687b2f229d2495.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- weight factors go here, similar to curve fitting sliders -->
|
||||
@@ -2421,7 +2429,7 @@ for(let L = 1; L <= order; L++) {
|
||||
<graphics-element title="A uniform B-Spline" width="400" height="400" src="./chapters/bsplines/uniform.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\7ac6e46280de14dce141fb073777dc8e.png">
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\3cfaf7bf5e950072437cfe70391155fa.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- knot sliders go here, similar to the curve fitter section -->
|
||||
@@ -2434,7 +2442,7 @@ for(let L = 1; L <= order; L++) {
|
||||
<graphics-element title="A reduced uniform B-Spline" width="400" height="400" src="./chapters/bsplines/reduced.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\984c86b3b357c799aaab5a5fbd48d599.png">
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\6ba59877389e2c856234403ecbd953f9.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- knot sliders go here, similar to the curve fitter section -->
|
||||
@@ -2447,7 +2455,7 @@ for(let L = 1; L <= order; L++) {
|
||||
<graphics-element title="An open, uniform B-Spline" width="400" height="400" src="./chapters/bsplines/uniform.js" data-open="true">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\4c71f82901edc1a0acae31cf5be12c65.png">
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\9c4bbb753918e3cb8cbc5a770a2af9ee.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- knot sliders go here, similar to the curve fitter section -->
|
||||
@@ -2461,7 +2469,7 @@ for(let L = 1; L <= order; L++) {
|
||||
<graphics-element title="A (closed) rational, uniform B-Spline" width="400" height="400" src="./chapters/bsplines/rational-uniform.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\9915d887443459415a7bfb57bea1b898.png">
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\dda4911d5148032e005a02ef33b78978.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- knot sliders go here, similar to the curve fitter section -->
|
||||
|
@@ -409,13 +409,13 @@ function Bezier(3,t,w[]):
|
||||
<graphics-element title="Our rational cubic Bézier curve" width="275" height="275" src="./chapters/weightcontrol/rational.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\weightcontrol\0ef06657f0540938686b9b35b249a22b.png">
|
||||
<img width="275px" height="275px" src="images\chapters\weightcontrol\be18e8119472af796329f3e2159bdf94.png">
|
||||
<label>Our rational cubic Bézier curve</label>
|
||||
</fallback-image>
|
||||
ratio 1 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
ratio 2 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
ratio 3 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
ratio 4 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span>
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-1">
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-2">
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-3">
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-4">
|
||||
</graphics-element>
|
||||
|
||||
<p>You can think of the ratio values as each coordinate's "gravity": the higher the gravity, the closer to that coordinate the curve will want to be. You'll also notice that if you simply increase or decrease all the ratios by the same amount, nothing changes... much like with gravity, if the relative strengths stay the same, nothing really changes. The values define each coordinate's influence <em>relative to all other points</em>.</p>
|
||||
@@ -738,7 +738,7 @@ function drawCurve(points[], t):
|
||||
<graphics-element title="A variable-order Bézier curve" width="275" height="275" src="./chapters/reordering/reorder.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\reordering\387f931043aabd6c467985c568482636.png">
|
||||
<img width="275px" height="275px" src="images\chapters\reordering\81e559a69298ecc24c8edebe59e79647.png">
|
||||
<label>A variable-order Bézier curve</label>
|
||||
</fallback-image>
|
||||
<button class="raise">raise</button>
|
||||
@@ -811,16 +811,16 @@ treated as a sequence of three (elementary) shear operations. When we combine th
|
||||
|
||||
<p>The following two graphics show the tangent and normal along a quadratic and cubic curve, with the direction vector coloured blue, and the normal vector coloured red (the markers are spaced out evenly as <em>t</em>-intervals, not spaced equidistant).</p>
|
||||
<div class="figure">
|
||||
<graphics-element title="Quadratic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/quadratic.js" >
|
||||
<graphics-element title="Quadratic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/pointvectors.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\44d454f380ae84dbe4661bd67c406edc.png">
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\f4d72cc162d4cbcc6d6a459fced01cfb.png">
|
||||
<label>Quadratic Bézier tangents and normals</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Cubic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/cubic.js" >
|
||||
<graphics-element title="Cubic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/pointvectors.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\58719bde12c1cbd535d5d8af1bef9c2b.png">
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\2724b6c42f36b4dbea6962cac844d2c3.png">
|
||||
<label>Cubic Bézier tangents and normals</label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
@@ -856,7 +856,7 @@ treated as a sequence of three (elementary) shear operations. When we combine th
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<p>However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around" around between t=0.5 and t=0.9... Why is doing that?</p>
|
||||
<p>However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around the curve" between t=0.65 and t=0.75... Why is it doing that?</p>
|
||||
<p>As it turns out, it's doing that because that's how the maths works, and that's the problem with Frenet normals: while they are "mathematically correct", they are "practically problematic", and so for any kind of graphics work what we really want is a way to compute normals that just... look good.</p>
|
||||
<p>Thankfully, Frenet normals are not our only option.</p>
|
||||
<p>Another option is to take a slightly more algorithmic approach and compute a form of <a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/12/Computation-of-rotation-minimizing-frames.pdf">Rotation Minimising Frame</a> (also known as "parallel transport frame" or "Bishop frame") instead, where a "frame" is a set made up of the tangent, the rotational axis, and the normal vector, centered on an on-curve point.</p>
|
||||
@@ -1355,19 +1355,19 @@ y = curve.get(t).y</code></pre>
|
||||
<graphics-element title="A function's approximated integral" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="10">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\580c33f599b70de44b17c546098508aa.png">
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\dc74a2f2da19470b8d721ece5f3ce268.png">
|
||||
<label>A function's approximated integral</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="A better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="24">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\0d7138b99f5986332a050a8479eefa57.png">
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\4bffba7dda2a3556cf5b2ae7392083c6.png">
|
||||
<label>A better approximation</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="An even better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="99">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\475547c773a7279dc037c9ced2c8c6dc.png">
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\4b5d220d02b08f6c9aa19389255ef8bb.png">
|
||||
<label>An even better approximation</label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
@@ -1408,7 +1408,7 @@ y = curve.get(t).y</code></pre>
|
||||
<img width="275px" height="275px" src="images\chapters\arclengthapprox\a040f6b7c7c33ada25ecfd1060726545.png">
|
||||
<label>Approximate quadratic curve arc length</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="1" max="24" step="1" value="4" class="slide-control">
|
||||
<input type="range" min="2" max="24" step="1" value="4" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="Approximate cubic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="cubic">
|
||||
@@ -1417,7 +1417,7 @@ y = curve.get(t).y</code></pre>
|
||||
<img width="275px" height="275px" src="images\chapters\arclengthapprox\c270144cc41e4ebc4b0b2331473530fa.png">
|
||||
<label>Approximate cubic curve arc length</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="1" max="32" step="1" value="8" class="slide-control">
|
||||
<input type="range" min="2" max="32" step="1" value="8" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
</div>
|
||||
@@ -1584,12 +1584,12 @@ lli = function(line1, line2):
|
||||
<graphics-element title="Curve/curve intersections" width="825" height="275" src="./chapters/curveintersection/curve-curve.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\curveintersection\9540ecc84c3fc63a07f5372ab031c99e.png">
|
||||
<img width="825px" height="275px" src="images\chapters\curveintersection\390fe022c4e290a7a9d3814ae2936b77.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
|
||||
<button class="next">Advance one step</button>
|
||||
<button class="reset">Reset</button>
|
||||
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<p>Finding self-intersections is effectively the same procedure, except that we're starting with a single curve, so we need to turn that into two separate curves first. This is trivially achieved by splitting at an inflection point, or if there are none, just splitting at <code>t=0.5</code> first, and then running the exact same algorithm as above, with all non-overlapping curve pairs getting removed at each iteration, and each successive step homing in on the curve's self-intersection points.</p>
|
||||
@@ -1720,10 +1720,10 @@ for (coordinate, index) in LUT:
|
||||
</ol>
|
||||
<p>This makes the interval we check smaller and smaller at each iteration, and we can keep running the three steps until the interval becomes so small as to lead to distances that are, for all intents and purposes, the same for all points.</p>
|
||||
<p>So, let's see that in action: in this case, I'm going to arbitrarily say that if we're going to run the loop until the interval is smaller than 0.001, and show you what that means for projecting your mouse cursor or finger tip onto a rather complex Bézier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.</p>
|
||||
<graphics-element title="Projecting a point onto a Bézier curve" width="320" height="320" src="./chapters/projections/project.js" >
|
||||
<graphics-element title="Projecting a point onto a Bézier curve" width="400" height="400" src="./chapters/projections/project.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="320px" height="320px" src="images\chapters\projections\c40ab9e3f3d1f53872dff30a7bcdb003.png">
|
||||
<img width="400px" height="400px" src="images\chapters\projections\3cc334d0ebc01cc5352e23ed47bc5414.png">
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -1843,11 +1843,11 @@ for (coordinate, index) in LUT:
|
||||
<graphics-element title="Fitting a Bézier curve" width="550" height="275" src="./chapters/curvefitting/curve-fitting.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="550px" height="275px" src="images\chapters\curvefitting\78d32beb061391c47217611128446146.png">
|
||||
<img width="550px" height="275px" src="images\chapters\curvefitting\1307361f152242ab0263d81a469cf43b.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<button class="toggle">toggle</button>
|
||||
<div class="sliders"><!-- this will contain range inputs, created by the graphic --></div>
|
||||
<!-- additional sliders will get created on the fly -->
|
||||
</graphics-element>
|
||||
|
||||
<p>You'll note there is a convenient "toggle" buttons that lets you toggle between equidistant <code>t</code> values, and distance ratio along the polygon formed by the points. Arguably more interesting is that once you have points to abstract a curve, you also get <em>direct control</em> over the time values through sliders for each, because if the time values are our degree of freedom, you should be able to freely manipulate them and see what the effect on your curve is.</p>
|
||||
@@ -1860,7 +1860,7 @@ for (coordinate, index) in LUT:
|
||||
<graphics-element title="A Catmull-Rom curve" width="275" height="275" src="./chapters/catmullconv/catmull-rom.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\catmullconv\aa46749b9469341d9249ca452390d875.png">
|
||||
<img width="275px" height="275px" src="images\chapters\catmullconv\e1e640b29dd07f905c1555438511cad9.png">
|
||||
<label>A Catmull-Rom curve</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0.1" max="1" step="0.01" value="0.5" class="slide-control tension">
|
||||
@@ -1988,13 +1988,13 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="Unlinked quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="coordinate">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\0553872bf00ebc557f4b11d69d634fc6.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\044f65dd588b0210499add16ea50a23d.png">
|
||||
<label>Unlinked quadratic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Unlinked cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="coordinate">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\60adbd6fe091a72e1ed37355e49a506e.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\babb27083b805c7c77bb93cfecbefb2b.png">
|
||||
<label>Unlinked cubic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -2006,13 +2006,13 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="Connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="derivative">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\1aeb331a5170c203c31c55ae8d55b809.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\b492c486c25f17a95c690e235b8ad483.png">
|
||||
<label>Connected quadratic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="derivative">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\04bc7b98ba019a4a90bedce6e10eaf08.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\adc3b55c9956849ec86ecffcd8864d8a.png">
|
||||
<label>Connected cubic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -2022,13 +2022,13 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="Angularly connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="direction">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\acb8f004751017eaac4aae0b039c94cf.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\17a6ffbfffaa9046ad165ca880d9030d.png">
|
||||
<label>Angularly connected quadratic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Angularly connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="direction">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\1a3c6b24bea874d32334d62110507fcb.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\b7dfe772ac90d762f48772b691a9070f.png">
|
||||
<label>Angularly connected cubic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -2037,13 +2037,13 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="Standard connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="conventional">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\7afd119dd93a581ec161e2cbfc3c1e63.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\a1a3aabd22c0221beb38403a4532ea86.png">
|
||||
<label>Standard connected quadratic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Standard connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="conventional">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\cfbfbbc56365ba547dc7e82b329c4007.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\b810f02639a79cf7f8ae416d7185614d.png">
|
||||
<label>Standard connected cubic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>>
|
||||
|
||||
@@ -2332,7 +2332,7 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="A B-Spline example" width="600" height="300" src="./chapters/bsplines/basic.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\610232b8f7ce7ef3f0f012d55e385c6d.png">
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\d9a99344a5de4b5be77824f8f8caa707.png">
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -2342,6 +2342,14 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<li>for Bézier curves, the curve is defined as an interpolation of points, but:</li>
|
||||
<li>for B-Splines, the curve is defined as an interpolation of <em>curves</em>.</li>
|
||||
</ul>
|
||||
<p>In fact, let's look at that again, but this time with the base curves shown, too. Each consecutive four points defined one curve:</p>
|
||||
<graphics-element title="The components of a B-Spline " width="600" height="300" src="./chapters/bsplines/basic.js" data-show-curves="true">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\3f66c97dcdd56d201c2acda3bd403bbf.png">
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>In order to make this interpolation of curves work, the maths is necessarily more complex than the maths for Bézier curves, so let's have a look at how things work.</p>
|
||||
<h2>How to compute a B-Spline curve: some maths</h2>
|
||||
<p>Given a B-Spline of degree <code>d</code> and thus order <code>k=d+1</code> (so a quadratic B-Spline is degree 2 and order 3, a cubic B-Spline is degree 3 and order 4, etc) and <code>n</code> control points <code>P<sub>0</sub></code> through <code>P<sub>n-1</sub></code>, we can compute a point on the curve for some value <code>t</code> in the interval [0,1] (where 0 is the start of the curve, and 1 the end, just like for Bézier curves), by evaluating the following function:</p>
|
||||
@@ -2375,7 +2383,7 @@ Doing so for a degree <code>d</code> B-Spline with <code>n</code> control point
|
||||
<graphics-element title="Visualising relative interpolation strengths" width="600" height="300" src="./chapters/bsplines/interpolation.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\84bd623b9d3d8bf01a656f49c17079b8.png">
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\bd03680e4661ae7f4d687b2f229d2495.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- weight factors go here, similar to curve fitting sliders -->
|
||||
@@ -2417,7 +2425,7 @@ for(let L = 1; L <= order; L++) {
|
||||
<graphics-element title="A uniform B-Spline" width="400" height="400" src="./chapters/bsplines/uniform.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\7ac6e46280de14dce141fb073777dc8e.png">
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\3cfaf7bf5e950072437cfe70391155fa.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- knot sliders go here, similar to the curve fitter section -->
|
||||
@@ -2430,7 +2438,7 @@ for(let L = 1; L <= order; L++) {
|
||||
<graphics-element title="A reduced uniform B-Spline" width="400" height="400" src="./chapters/bsplines/reduced.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\984c86b3b357c799aaab5a5fbd48d599.png">
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\6ba59877389e2c856234403ecbd953f9.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- knot sliders go here, similar to the curve fitter section -->
|
||||
@@ -2443,7 +2451,7 @@ for(let L = 1; L <= order; L++) {
|
||||
<graphics-element title="An open, uniform B-Spline" width="400" height="400" src="./chapters/bsplines/uniform.js" data-open="true">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\4c71f82901edc1a0acae31cf5be12c65.png">
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\9c4bbb753918e3cb8cbc5a770a2af9ee.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- knot sliders go here, similar to the curve fitter section -->
|
||||
@@ -2457,7 +2465,7 @@ for(let L = 1; L <= order; L++) {
|
||||
<graphics-element title="A (closed) rational, uniform B-Spline" width="400" height="400" src="./chapters/bsplines/rational-uniform.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\9915d887443459415a7bfb57bea1b898.png">
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\dda4911d5148032e005a02ef33b78978.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- knot sliders go here, similar to the curve fitter section -->
|
||||
|
@@ -207,8 +207,32 @@ class GraphicsAPI extends BaseAPI {
|
||||
throw new Error(`this.${propname} already exists: cannot bind slider.`);
|
||||
}
|
||||
|
||||
let propLabel = propname.replace(`!`, ``);
|
||||
propname = propLabel === propname ? propname : false;
|
||||
|
||||
let slider = typeof qs === `string` ? this.find(qs) : qs;
|
||||
|
||||
// relocate this slider
|
||||
let ui = (() => {
|
||||
if (!this.element) {
|
||||
return { update: (v) => {} };
|
||||
}
|
||||
let wrapper = create(`div`);
|
||||
wrapper.classList.add(`slider-wrapper`);
|
||||
let label = create(`label`);
|
||||
label.classList.add(`slider-label`);
|
||||
label.innerHTML = propLabel;
|
||||
wrapper.append(label);
|
||||
slider.parentNode.replaceChild(wrapper, slider);
|
||||
slider.setAttribute(`class`, `slider`);
|
||||
wrapper.append(slider);
|
||||
let valueField = create(`label`);
|
||||
valueField.classList.add(`slider-value`);
|
||||
valueField.textContent;
|
||||
wrapper.append(valueField);
|
||||
return { update: (v) => (valueField.textContent = v) };
|
||||
})();
|
||||
|
||||
if (!slider) {
|
||||
console.warn(`Warning: no slider found for query selector "${qs}"`);
|
||||
if (propname) this[propname] = initial;
|
||||
@@ -217,6 +241,7 @@ class GraphicsAPI extends BaseAPI {
|
||||
|
||||
const updateProperty = (evt) => {
|
||||
let value = parseFloat(slider.value);
|
||||
ui.update(value);
|
||||
try {
|
||||
let checked = transform ? transform(value) ?? value : value;
|
||||
if (propname) this[propname] = checked;
|
||||
@@ -225,6 +250,7 @@ class GraphicsAPI extends BaseAPI {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
}
|
||||
ui.update(e.value);
|
||||
slider.value = e.value;
|
||||
slider.setAttribute(`value`, e.value);
|
||||
}
|
||||
@@ -242,7 +268,7 @@ class GraphicsAPI extends BaseAPI {
|
||||
* remove all sliders from this element
|
||||
*/
|
||||
removeSliders() {
|
||||
this.findAll(`input[type=range]`).forEach((s) => {
|
||||
this.findAll(`.slider-wrapper`).forEach((s) => {
|
||||
s.parentNode.removeChild(s);
|
||||
});
|
||||
}
|
||||
|
@@ -1,14 +1,27 @@
|
||||
import { enrich } from "./enrich.js";
|
||||
|
||||
function create(element) {
|
||||
const noop = () => {};
|
||||
|
||||
function create(tag) {
|
||||
if (typeof document !== `undefined`) {
|
||||
return enrich(document.createElement(element));
|
||||
return enrich(document.createElement(tag));
|
||||
}
|
||||
|
||||
return {
|
||||
name: element,
|
||||
tag: element.toUpperCase(),
|
||||
const element = {
|
||||
name: tag,
|
||||
tag: tag.toUpperCase(),
|
||||
append: noop,
|
||||
appendChild: noop,
|
||||
replaceChild: noop,
|
||||
removeChild: noop,
|
||||
classList: {
|
||||
add: noop,
|
||||
remove: noop,
|
||||
},
|
||||
children: [],
|
||||
};
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
export { create };
|
||||
|
@@ -1,153 +1,193 @@
|
||||
:root[lang="en-GB"] {
|
||||
font-family: 'Museo', 'Helvetica Neue', 'Helvetica', Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
font-family: "Museo", "Helvetica Neue", "Helvetica", Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
:root[lang="ja-JP"] {
|
||||
font-family: 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', 'Osaka', 'メイリオ', 'Meiryo', 'MS Pゴシック', 'MS PGothic', 'Helvetica Neue', 'Helvetica', Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
font-family: "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", "Osaka",
|
||||
"メイリオ", "Meiryo", "MS Pゴシック", "MS PGothic", "Helvetica Neue",
|
||||
"Helvetica", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
:root[lang="zh-CN"] {
|
||||
font-family: '华文细黑', 'STXihei', 'PingFang TC', '微软雅黑体', 'Microsoft YaHei New', '微软雅黑', 'Microsoft Yahei', '宋体', 'SimSun', 'Helvetica Neue', 'Helvetica', Arial, sans-serif;
|
||||
font-size: 17.2px;
|
||||
font-family: "华文细黑", "STXihei", "PingFang TC", "微软雅黑体",
|
||||
"Microsoft YaHei New", "微软雅黑", "Microsoft Yahei", "宋体", "SimSun",
|
||||
"Helvetica Neue", "Helvetica", Arial, sans-serif;
|
||||
font-size: 17.2px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
background: black;
|
||||
color: white;
|
||||
padding: 0.2em 0.5em;
|
||||
background: black;
|
||||
color: white;
|
||||
padding: 0.2em 0.5em;
|
||||
}
|
||||
|
||||
label[for="changelogtoggle"] {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
color: #2929b3;
|
||||
margin-left: 2em;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
color: #2929b3;
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
#changelogtoggle {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#changelogtoggle:not(:checked) ~ * {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
#chapters {
|
||||
counter-reset: section;
|
||||
counter-reset: section;
|
||||
}
|
||||
|
||||
#chapters section h1:before {
|
||||
counter-increment: section;
|
||||
content: "§" counter(section);
|
||||
margin-right: 1em;
|
||||
counter-increment: section;
|
||||
content: "§" counter(section);
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#chapters section > h1 > a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
graphics-element {
|
||||
background: #e8e8e8;
|
||||
background: #e8e8e8;
|
||||
}
|
||||
|
||||
div.print {
|
||||
opacity: 0.2;
|
||||
padding-left: 2em;
|
||||
opacity: 0.2;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
div.print:before {
|
||||
content: "PRINT-ONLY:";
|
||||
position: relative;
|
||||
left: -2em;
|
||||
content: "PRINT-ONLY:";
|
||||
position: relative;
|
||||
left: -2em;
|
||||
}
|
||||
|
||||
div.print:after {
|
||||
content: "END-PRINT-ONLY";
|
||||
position: relative;
|
||||
left: -2em;
|
||||
content: "END-PRINT-ONLY";
|
||||
position: relative;
|
||||
left: -2em;
|
||||
}
|
||||
|
||||
div.howtocode {
|
||||
position: relative;
|
||||
border: 1px solid black;
|
||||
background: rgb(246, 255, 255);
|
||||
margin: 1em;
|
||||
padding: 1em 1em 0 1em;
|
||||
position: relative;
|
||||
border: 1px solid black;
|
||||
background: rgb(246, 255, 255);
|
||||
margin: 1em;
|
||||
padding: 1em 1em 0 1em;
|
||||
}
|
||||
|
||||
div.howtocode h3 {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: calc(100% + 2em - 4px);
|
||||
left: calc(-1em + 2px);
|
||||
background: black;
|
||||
color: white;
|
||||
padding: 0.2em;
|
||||
margin: 0 auto;
|
||||
margin-top: -1em;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: calc(100% + 2em - 4px);
|
||||
left: calc(-1em + 2px);
|
||||
background: black;
|
||||
color: white;
|
||||
padding: 0.2em;
|
||||
margin: 0 auto;
|
||||
margin-top: -1em;
|
||||
}
|
||||
|
||||
div.note {
|
||||
position: relative;
|
||||
border: 1px solid black;
|
||||
background: rgb(255, 255, 246);
|
||||
margin: 1em;
|
||||
padding: 1em 1em 0 1em;
|
||||
position: relative;
|
||||
border: 1px solid black;
|
||||
background: rgb(255, 255, 246);
|
||||
margin: 1em;
|
||||
padding: 1em 1em 0 1em;
|
||||
}
|
||||
|
||||
div.note:before {
|
||||
display: inline-block;
|
||||
content: "Note";
|
||||
border: 1px solid black;
|
||||
border-radius: 0.5em;
|
||||
background: orange;
|
||||
padding: 0.2em 0.5em;
|
||||
position: absolute;
|
||||
top: -0.5em;
|
||||
left: -1em;
|
||||
display: inline-block;
|
||||
content: "Note";
|
||||
border: 1px solid black;
|
||||
border-radius: 0.5em;
|
||||
background: orange;
|
||||
padding: 0.2em 0.5em;
|
||||
position: absolute;
|
||||
top: -0.5em;
|
||||
left: -1em;
|
||||
}
|
||||
|
||||
img.LaTeX.SVG {
|
||||
margin-left: 2em;
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
img.LaTeX.SVG + img.LaTeX.SVG {
|
||||
margin-top: 1em;
|
||||
margin-left: 1em;
|
||||
margin-top: 1em;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
background-color: lightyellow;
|
||||
padding: 0.5em;
|
||||
border: 1px dotted gray;
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
background-color: lightyellow;
|
||||
padding: 0.5em;
|
||||
border: 1px dotted gray;
|
||||
}
|
||||
|
||||
code {
|
||||
position: relative;
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
position: relative;
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
p code {
|
||||
color: #ff6000;
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
font-size: 1.2em;
|
||||
letter-spacing: -2px;
|
||||
color: #ff6000;
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
font-size: 1.2em;
|
||||
letter-spacing: -2px;
|
||||
}
|
||||
|
||||
.slide-control {
|
||||
display: block;
|
||||
width: 100%;
|
||||
.slider-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
font-size: 90%;
|
||||
background: white;
|
||||
margin: -0.5em;
|
||||
margin-left: -0.25em;
|
||||
margin-right: -0.25em;
|
||||
border: 1px solid black;
|
||||
border-width: 0 1px;
|
||||
}
|
||||
|
||||
graphics-element .slider-wrapper:first-of-type {
|
||||
clear: both;
|
||||
border-top: 1px solid black;
|
||||
margin-top: 0.1em;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
graphics-element .slider-wrapper:last-of-type {
|
||||
border-bottom: 1px solid black;
|
||||
margin-bottom: -0.25em;
|
||||
}
|
||||
|
||||
.slider-wrapper .slider-label {
|
||||
flex: 0 0 auto;
|
||||
font-family: Arial;
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
.slider-wrapper .slider-value {
|
||||
flex: 0 0 auto;
|
||||
min-width: 3em;
|
||||
padding-right: 0.25em;
|
||||
text-align-last: center;
|
||||
}
|
||||
|
||||
.slider-wrapper .slider {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
@@ -403,13 +403,13 @@ function Bezier(3,t,w[]):
|
||||
<graphics-element title="Our rational cubic Bézier curve" width="275" height="275" src="./chapters/weightcontrol/rational.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\weightcontrol\0ef06657f0540938686b9b35b249a22b.png">
|
||||
<img width="275px" height="275px" src="images\chapters\weightcontrol\be18e8119472af796329f3e2159bdf94.png">
|
||||
<label>Our rational cubic Bézier curve</label>
|
||||
</fallback-image>
|
||||
ratio 1 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
ratio 2 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
ratio 3 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span><br>
|
||||
ratio 4 <input type="range" min="0.01" max="2" value="1" step="0.01"><span>1.0</span>
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-1">
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-2">
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-3">
|
||||
<input type="range" min="0.01" max="2" value="1" step="0.01" class="ratio-4">
|
||||
</graphics-element>
|
||||
|
||||
<p>You can think of the ratio values as each coordinate's "gravity": the higher the gravity, the closer to that coordinate the curve will want to be. You'll also notice that if you simply increase or decrease all the ratios by the same amount, nothing changes... much like with gravity, if the relative strengths stay the same, nothing really changes. The values define each coordinate's influence <em>relative to all other points</em>.</p>
|
||||
@@ -732,7 +732,7 @@ function drawCurve(points[], t):
|
||||
<graphics-element title="A variable-order Bézier curve" width="275" height="275" src="./chapters/reordering/reorder.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\reordering\387f931043aabd6c467985c568482636.png">
|
||||
<img width="275px" height="275px" src="images\chapters\reordering\81e559a69298ecc24c8edebe59e79647.png">
|
||||
<label>A variable-order Bézier curve</label>
|
||||
</fallback-image>
|
||||
<button class="raise">raise</button>
|
||||
@@ -805,16 +805,16 @@ treated as a sequence of three (elementary) shear operations. When we combine th
|
||||
|
||||
<p>The following two graphics show the tangent and normal along a quadratic and cubic curve, with the direction vector coloured blue, and the normal vector coloured red (the markers are spaced out evenly as <em>t</em>-intervals, not spaced equidistant).</p>
|
||||
<div class="figure">
|
||||
<graphics-element title="Quadratic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/quadratic.js" >
|
||||
<graphics-element title="Quadratic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/pointvectors.js" data-type="quadratic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\44d454f380ae84dbe4661bd67c406edc.png">
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\f4d72cc162d4cbcc6d6a459fced01cfb.png">
|
||||
<label>Quadratic Bézier tangents and normals</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Cubic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/cubic.js" >
|
||||
<graphics-element title="Cubic Bézier tangents and normals" width="275" height="275" src="./chapters/pointvectors/pointvectors.js" data-type="cubic">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\58719bde12c1cbd535d5d8af1bef9c2b.png">
|
||||
<img width="275px" height="275px" src="images\chapters\pointvectors\2724b6c42f36b4dbea6962cac844d2c3.png">
|
||||
<label>Cubic Bézier tangents and normals</label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
@@ -850,7 +850,7 @@ treated as a sequence of three (elementary) shear operations. When we combine th
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<p>However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around" around between t=0.5 and t=0.9... Why is doing that?</p>
|
||||
<p>However, if you've played with that graphic a bit, you might have noticed something odd. The normal seems to "suddenly twist around the curve" between t=0.65 and t=0.75... Why is it doing that?</p>
|
||||
<p>As it turns out, it's doing that because that's how the maths works, and that's the problem with Frenet normals: while they are "mathematically correct", they are "practically problematic", and so for any kind of graphics work what we really want is a way to compute normals that just... look good.</p>
|
||||
<p>Thankfully, Frenet normals are not our only option.</p>
|
||||
<p>Another option is to take a slightly more algorithmic approach and compute a form of <a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2016/12/Computation-of-rotation-minimizing-frames.pdf">Rotation Minimising Frame</a> (also known as "parallel transport frame" or "Bishop frame") instead, where a "frame" is a set made up of the tangent, the rotational axis, and the normal vector, centered on an on-curve point.</p>
|
||||
@@ -1349,19 +1349,19 @@ y = curve.get(t).y</code></pre>
|
||||
<graphics-element title="A function's approximated integral" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="10">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\580c33f599b70de44b17c546098508aa.png">
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\dc74a2f2da19470b8d721ece5f3ce268.png">
|
||||
<label>A function's approximated integral</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="A better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="24">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\0d7138b99f5986332a050a8479eefa57.png">
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\4bffba7dda2a3556cf5b2ae7392083c6.png">
|
||||
<label>A better approximation</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="An even better approximation" width="275" height="275" src="./chapters/arclength/draw-slices.js" data-steps="99">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\475547c773a7279dc037c9ced2c8c6dc.png">
|
||||
<img width="275px" height="275px" src="images\chapters\arclength\4b5d220d02b08f6c9aa19389255ef8bb.png">
|
||||
<label>An even better approximation</label>
|
||||
</fallback-image></graphics-element>
|
||||
</div>
|
||||
@@ -1402,7 +1402,7 @@ y = curve.get(t).y</code></pre>
|
||||
<img width="275px" height="275px" src="images\chapters\arclengthapprox\a040f6b7c7c33ada25ecfd1060726545.png">
|
||||
<label>Approximate quadratic curve arc length</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="1" max="24" step="1" value="4" class="slide-control">
|
||||
<input type="range" min="2" max="24" step="1" value="4" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<graphics-element title="Approximate cubic curve arc length" width="275" height="275" src="./chapters/arclengthapprox/approximate.js" data-type="cubic">
|
||||
@@ -1411,7 +1411,7 @@ y = curve.get(t).y</code></pre>
|
||||
<img width="275px" height="275px" src="images\chapters\arclengthapprox\c270144cc41e4ebc4b0b2331473530fa.png">
|
||||
<label>Approximate cubic curve arc length</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="1" max="32" step="1" value="8" class="slide-control">
|
||||
<input type="range" min="2" max="32" step="1" value="8" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
</div>
|
||||
@@ -1578,12 +1578,12 @@ lli = function(line1, line2):
|
||||
<graphics-element title="Curve/curve intersections" width="825" height="275" src="./chapters/curveintersection/curve-curve.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="825px" height="275px" src="images\chapters\curveintersection\9540ecc84c3fc63a07f5372ab031c99e.png">
|
||||
<img width="825px" height="275px" src="images\chapters\curveintersection\390fe022c4e290a7a9d3814ae2936b77.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
|
||||
<button class="next">Advance one step</button>
|
||||
<button class="reset">Reset</button>
|
||||
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
|
||||
</graphics-element>
|
||||
|
||||
<p>Finding self-intersections is effectively the same procedure, except that we're starting with a single curve, so we need to turn that into two separate curves first. This is trivially achieved by splitting at an inflection point, or if there are none, just splitting at <code>t=0.5</code> first, and then running the exact same algorithm as above, with all non-overlapping curve pairs getting removed at each iteration, and each successive step homing in on the curve's self-intersection points.</p>
|
||||
@@ -1714,10 +1714,10 @@ for (coordinate, index) in LUT:
|
||||
</ol>
|
||||
<p>This makes the interval we check smaller and smaller at each iteration, and we can keep running the three steps until the interval becomes so small as to lead to distances that are, for all intents and purposes, the same for all points.</p>
|
||||
<p>So, let's see that in action: in this case, I'm going to arbitrarily say that if we're going to run the loop until the interval is smaller than 0.001, and show you what that means for projecting your mouse cursor or finger tip onto a rather complex Bézier curve (which, of course, you can reshape as you like). Also shown are the original three points that our coarse check finds.</p>
|
||||
<graphics-element title="Projecting a point onto a Bézier curve" width="320" height="320" src="./chapters/projections/project.js" >
|
||||
<graphics-element title="Projecting a point onto a Bézier curve" width="400" height="400" src="./chapters/projections/project.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="320px" height="320px" src="images\chapters\projections\c40ab9e3f3d1f53872dff30a7bcdb003.png">
|
||||
<img width="400px" height="400px" src="images\chapters\projections\3cc334d0ebc01cc5352e23ed47bc5414.png">
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -1837,11 +1837,11 @@ for (coordinate, index) in LUT:
|
||||
<graphics-element title="Fitting a Bézier curve" width="550" height="275" src="./chapters/curvefitting/curve-fitting.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="550px" height="275px" src="images\chapters\curvefitting\78d32beb061391c47217611128446146.png">
|
||||
<img width="550px" height="275px" src="images\chapters\curvefitting\1307361f152242ab0263d81a469cf43b.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<button class="toggle">toggle</button>
|
||||
<div class="sliders"><!-- this will contain range inputs, created by the graphic --></div>
|
||||
<!-- additional sliders will get created on the fly -->
|
||||
</graphics-element>
|
||||
|
||||
<p>You'll note there is a convenient "toggle" buttons that lets you toggle between equidistant <code>t</code> values, and distance ratio along the polygon formed by the points. Arguably more interesting is that once you have points to abstract a curve, you also get <em>direct control</em> over the time values through sliders for each, because if the time values are our degree of freedom, you should be able to freely manipulate them and see what the effect on your curve is.</p>
|
||||
@@ -1854,7 +1854,7 @@ for (coordinate, index) in LUT:
|
||||
<graphics-element title="A Catmull-Rom curve" width="275" height="275" src="./chapters/catmullconv/catmull-rom.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\catmullconv\aa46749b9469341d9249ca452390d875.png">
|
||||
<img width="275px" height="275px" src="images\chapters\catmullconv\e1e640b29dd07f905c1555438511cad9.png">
|
||||
<label>A Catmull-Rom curve</label>
|
||||
</fallback-image>
|
||||
<input type="range" min="0.1" max="1" step="0.01" value="0.5" class="slide-control tension">
|
||||
@@ -1982,13 +1982,13 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="Unlinked quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="coordinate">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\0553872bf00ebc557f4b11d69d634fc6.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\044f65dd588b0210499add16ea50a23d.png">
|
||||
<label>Unlinked quadratic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Unlinked cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="coordinate">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\60adbd6fe091a72e1ed37355e49a506e.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\babb27083b805c7c77bb93cfecbefb2b.png">
|
||||
<label>Unlinked cubic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -2000,13 +2000,13 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="Connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="derivative">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\1aeb331a5170c203c31c55ae8d55b809.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\b492c486c25f17a95c690e235b8ad483.png">
|
||||
<label>Connected quadratic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="derivative">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\04bc7b98ba019a4a90bedce6e10eaf08.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\adc3b55c9956849ec86ecffcd8864d8a.png">
|
||||
<label>Connected cubic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -2016,13 +2016,13 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="Angularly connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="direction">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\acb8f004751017eaac4aae0b039c94cf.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\17a6ffbfffaa9046ad165ca880d9030d.png">
|
||||
<label>Angularly connected quadratic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Angularly connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="direction">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\1a3c6b24bea874d32334d62110507fcb.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\b7dfe772ac90d762f48772b691a9070f.png">
|
||||
<label>Angularly connected cubic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -2031,13 +2031,13 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="Standard connected quadratic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="quadratic" data-link="conventional">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\7afd119dd93a581ec161e2cbfc3c1e63.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\a1a3aabd22c0221beb38403a4532ea86.png">
|
||||
<label>Standard connected quadratic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>
|
||||
<graphics-element title="Standard connected cubic poly-Bézier" width="275" height="275" src="./chapters/polybezier/poly.js" data-type="cubic" data-link="conventional">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\cfbfbbc56365ba547dc7e82b329c4007.png">
|
||||
<img width="275px" height="275px" src="images\chapters\polybezier\b810f02639a79cf7f8ae416d7185614d.png">
|
||||
<label>Standard connected cubic poly-Bézier</label>
|
||||
</fallback-image></graphics-element>>
|
||||
|
||||
@@ -2326,7 +2326,7 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<graphics-element title="A B-Spline example" width="600" height="300" src="./chapters/bsplines/basic.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\610232b8f7ce7ef3f0f012d55e385c6d.png">
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\d9a99344a5de4b5be77824f8f8caa707.png">
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
@@ -2336,6 +2336,14 @@ for p = 1 to points.length-3 (inclusive):
|
||||
<li>for Bézier curves, the curve is defined as an interpolation of points, but:</li>
|
||||
<li>for B-Splines, the curve is defined as an interpolation of <em>curves</em>.</li>
|
||||
</ul>
|
||||
<p>In fact, let's look at that again, but this time with the base curves shown, too. Each consecutive four points defined one curve:</p>
|
||||
<graphics-element title="The components of a B-Spline " width="600" height="300" src="./chapters/bsplines/basic.js" data-show-curves="true">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\3f66c97dcdd56d201c2acda3bd403bbf.png">
|
||||
<label></label>
|
||||
</fallback-image></graphics-element>
|
||||
|
||||
<p>In order to make this interpolation of curves work, the maths is necessarily more complex than the maths for Bézier curves, so let's have a look at how things work.</p>
|
||||
<h2>How to compute a B-Spline curve: some maths</h2>
|
||||
<p>Given a B-Spline of degree <code>d</code> and thus order <code>k=d+1</code> (so a quadratic B-Spline is degree 2 and order 3, a cubic B-Spline is degree 3 and order 4, etc) and <code>n</code> control points <code>P<sub>0</sub></code> through <code>P<sub>n-1</sub></code>, we can compute a point on the curve for some value <code>t</code> in the interval [0,1] (where 0 is the start of the curve, and 1 the end, just like for Bézier curves), by evaluating the following function:</p>
|
||||
@@ -2369,7 +2377,7 @@ Doing so for a degree <code>d</code> B-Spline with <code>n</code> control point
|
||||
<graphics-element title="Visualising relative interpolation strengths" width="600" height="300" src="./chapters/bsplines/interpolation.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\84bd623b9d3d8bf01a656f49c17079b8.png">
|
||||
<img width="600px" height="300px" src="images\chapters\bsplines\bd03680e4661ae7f4d687b2f229d2495.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- weight factors go here, similar to curve fitting sliders -->
|
||||
@@ -2411,7 +2419,7 @@ for(let L = 1; L <= order; L++) {
|
||||
<graphics-element title="A uniform B-Spline" width="400" height="400" src="./chapters/bsplines/uniform.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\7ac6e46280de14dce141fb073777dc8e.png">
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\3cfaf7bf5e950072437cfe70391155fa.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- knot sliders go here, similar to the curve fitter section -->
|
||||
@@ -2424,7 +2432,7 @@ for(let L = 1; L <= order; L++) {
|
||||
<graphics-element title="A reduced uniform B-Spline" width="400" height="400" src="./chapters/bsplines/reduced.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\984c86b3b357c799aaab5a5fbd48d599.png">
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\6ba59877389e2c856234403ecbd953f9.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- knot sliders go here, similar to the curve fitter section -->
|
||||
@@ -2437,7 +2445,7 @@ for(let L = 1; L <= order; L++) {
|
||||
<graphics-element title="An open, uniform B-Spline" width="400" height="400" src="./chapters/bsplines/uniform.js" data-open="true">
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\4c71f82901edc1a0acae31cf5be12c65.png">
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\9c4bbb753918e3cb8cbc5a770a2af9ee.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- knot sliders go here, similar to the curve fitter section -->
|
||||
@@ -2451,7 +2459,7 @@ for(let L = 1; L <= order; L++) {
|
||||
<graphics-element title="A (closed) rational, uniform B-Spline" width="400" height="400" src="./chapters/bsplines/rational-uniform.js" >
|
||||
<fallback-image>
|
||||
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\9915d887443459415a7bfb57bea1b898.png">
|
||||
<img width="400px" height="400px" src="images\chapters\bsplines\dda4911d5148032e005a02ef33b78978.png">
|
||||
<label></label>
|
||||
</fallback-image>
|
||||
<!-- knot sliders go here, similar to the curve fitter section -->
|
||||
|