1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-31 03:59:58 +02:00

generalised curve fitting

This commit is contained in:
Pomax
2020-09-01 22:28:09 -07:00
parent fc13f64451
commit 4aef67e662
37 changed files with 500 additions and 210 deletions

View File

@@ -1,3 +0,0 @@
## Additionals
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.96.5193&rep=rep1&type=pdf ?

View File

@@ -251,13 +251,11 @@ Here, the "to the power negative one" is the notation for the [matrix inverse](h
So before we try that out, how much code is involved in implementing this? Honestly, that answer depends on how much you're going to be writing yourself. If you already have a matrix maths library available, then really not that much code at all. On the other hand, if you are writing this from scratch, you're going to have to write some utility functions for doing your matrix work for you, so it's really anywhere from 50 lines of code to maybe 200 lines of code. Not a bad price to pay for being able to fit curves to prespecified coordinates.
So let's try it out! The following graphic lets you place points, and will start computing exact-fit curves once you've placed at least three. You can click for more points, and the code will simply try to compute an exact fit using a Bézier curve of the appropriate order. Four points? Cubic Bézier. Five points? Quartic. And so on. Of course, this does break down at some point: depending on where you place your points, it might become mighty hard for the fitter to find an exact fit, and things might actually start looking horribly off once you hit 10<sup>th</sup> or higher order curves. But it might not!
So let's try it out! The following graphic lets you place points, and will start computing exact-fit curves once you've placed at least three. You can click for more points, and the code will simply try to compute an exact fit using a Bézier curve of the appropriate order. Four points? Cubic Bézier. Five points? Quartic. And so on. Of course, this does break down at some point: depending on where you place your points, it might become mighty hard for the fitter to find an exact fit, and things might actually start looking horribly off once there's enough points for compound [floating point rounding errors](https://en.wikipedia.org/wiki/Round-off_error#Floating-point_number_system) to start making a difference (which is around 10~11 points).
<div class="figure">
<Graphic title="Fitting a Bézier curve" setup={this.setup} draw={this.draw} onClick={this.onClick}>
<button onClick={this.toggle} style="position:absolute; right: 0;">toggle</button>
<SliderSet ref={ set => (this.sliders=set) } onChange={this.processTimeUpdate} />
</Graphic>
</div>
<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>
</graphics-element>
You'll note there is a convenient "toggle" buttons that lets you toggle between equidistance `t` values, and distance ratio along the polygon. 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.
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.

View File

@@ -0,0 +1,148 @@
import invert from "./matrix-invert.js";
var binomialCoefficients = [[1],[1,1]];
function binomial(n,k) {
if (n===0) return 1;
var lut = binomialCoefficients;
while(n >= lut.length) {
var s = lut.length;
var nextRow = [1];
for(var i=1,prev=s-1; i<s; i++) {
nextRow[i] = lut[prev][i-1] + lut[prev][i];
}
nextRow[s] = 1;
lut.push(nextRow);
}
return lut[n][k];
}
function transpose(M) {
var Mt = [];
M.forEach(row => Mt.push([]));
M.forEach((row,r) => row.forEach((v,c) => Mt[c][r] = v));
return Mt;
}
function row(M,i) {
return M[i];
}
function col(M,i) {
var col = [];
for(var r=0, l=M.length; r<l; r++) {
col.push(M[r][i]);
}
return col;
}
function multiply(M1, M2) {
// prep
var M = [];
var dims = [M1.length, M1[0].length, M2.length, M2[0].length];
// work
for (var r=0, c; r<dims[0]; r++) {
M[r] = [];
var _row = row(M1, r);
for (c=0; c<dims[3]; c++) {
var _col = col(M2,c);
var reducer = (a,v,i) => a + _col[i]*v;
M[r][c] = _row.reduce(reducer, 0);
}
}
return M;
}
function getValueColumn(P, prop) {
var col = [];
P.forEach(v => col.push([v[prop]]));
return col;
}
function computeBasisMatrix(n) {
/*
We can form any basis matrix using a generative approach:
- it's an M = (n x n) matrix
- it's a lower triangular matrix: all the entries above the main diagonal are zero
- the main diagonal consists of the binomial coefficients for n
- all entries are symmetric about the antidiagonal.
What's more, if we number rows and columns starting at 0, then
the value at position M[r,c], with row=r and column=c, can be
expressed as:
M[r,c] = (r choose c) * M[r,r] * S,
where S = 1 if r+c is even, or -1 otherwise
That is: the values in column c are directly computed off of the
binomial coefficients on the main diagonal, through multiplication
by a binomial based on matrix position, with the sign of the value
also determined by matrix position. This is actually very easy to
write out in code:
*/
// form the square matrix, and set it to all zeroes
var M = [], i = n;
while (i--) { M[i] = "0".repeat(n).split('').map(v => parseInt(v)); }
// populate the main diagonal
var k = n - 1;
for (i=0; i<n; i++) { M[i][i] = binomial(k, i); }
// compute the remaining values
for (var c=0, r; c<n; c++) {
for (r=c+1; r<n; r++) {
var sign = (r+c)%2 ? -1 : 1;
var value = binomial(r, c) * M[r][r];
M[r][c] = sign * value; }}
return M;
}
function raiseRowPower(row, i) {
return row.map(v => Math.pow(v,i));
}
function formTMatrix(S, n) {
n = n || S.length;
var Tp = [];
// it's easier to generate the transposed matrix:
for(var i=0; i<n; i++) Tp.push( raiseRowPower(S, i));
return {
Tt: Tp,
T: transpose(Tp) // and then transpose "again" to get the real matrix
};
}
function computeBestFit(P, M, S, n) {
n = n || P.length;
var tm = formTMatrix(S, n),
T = tm.T,
Tt = tm.Tt,
M1 = invert(M),
TtT1 = invert(multiply(Tt,T)),
step1 = multiply(TtT1, Tt),
step2 = multiply(M1, step1),
X = getValueColumn(P,'x'),
Cx = multiply(step2, X),
Y = getValueColumn(P,'y'),
Cy = multiply(step2, Y);
return { x: Cx, y: Cy };
}
function fit(points, tvalues) {
// mode could be an int index to fit.modes, below,
// which are used to abstract time values, OR it
// could be a prespecified array of time values to
// be used in the final curve fitting step.
const n = points.length,
P = Array.from(points),
M = computeBasisMatrix(n),
S = tvalues,
C = computeBestFit(P, M, S, n);
return { n, P, M, S, C };
}
export default fit;

View File

@@ -0,0 +1,113 @@
import fit from "./curve-fitter.js";
let points = [], curve, sliders;
setup() {
let btn = find(`.toggle`);
if (btn) btn.listen(`click`, evt => this.toggle());
sliders = find(`.sliders`);
this.mode = 0;
this.label = `Using equidistant t values`;
}
toggle() {
this.mode = (this.mode + 1) % 2;
if (sliders) this.setSliderValues(this.mode);
redraw();
}
draw() {
clear();
setColor('lightgrey');
drawGrid(10);
setColor('black');
setFontSize(16);
setTextStroke(`white`, 4);
if (points.length > 2) {
curve = this.fitCurve(points);
curve.drawSkeleton(`blue`);
curve.drawCurve();
text(this.label, this.width/2, 20, CENTER);
}
points.forEach(p => circle(p.x, p.y, 3));
}
fitCurve(points) {
let n = points.length;
let tvalues = sliders ? sliders.values : [...new Array(n)].map((_,i) =>i/(n-1));
let bestFitData = fit(points, tvalues),
x = bestFitData.C.x,
y = bestFitData.C.y,
bpoints = x.map((r,i) => (
{x: r[0], y: y[i][0]}
));
return new Bezier(this, bpoints);
}
updateSliders() {
if (sliders && points.length > 2) {
sliders.innerHTML = ``;
sliders.values = [];
this.sliders = points.map((p,i) => {
// TODO: this should probably be built into the graphics API as a
// things that you can do, e.g. clearSliders() and addSlider()
let s = document.createElement(`input`);
s.setAttribute(`type`, `range`);
s.setAttribute(`min`, `0`);
s.setAttribute(`max`, `1`);
s.setAttribute(`step`, `0.01`);
s.classList.add(`slide-control`);
sliders.values[i] = i/(points.length-1);
s.setAttribute(`value`, sliders.values[i]);
s.addEventListener(`input`, evt => {
this.label = `Using custom t values`;
sliders.values[i] = parseFloat(evt.target.value);
redraw();
});
sliders.append(s);
});
}
}
setSliderValues(mode) {
let n = points.length;
// equidistant
if (mode === 0) {
this.label = `Using equidistant t values`;
sliders.values = [...new Array(n)].map((_,i) =>i/(n-1));
}
// polygonal distance
if (mode === 1) {
this.label = `Using polygonal distance t values`;
const D = [0];
for(let i = 1; i<n; i++) {
D[i] = D[i-1] + dist(
points[i-1].x, points[i-1].y,
points[i].x, points[i].y
);
}
const S = [], len = D[n-1];
D.forEach((v,i) => { S[i] = v/len; });
sliders.values = S;
}
findAll(`.sliders input[type=range]`).forEach((s,i) => {
s.setAttribute(`value`, sliders.values[i]);
s.value = sliders.values[i];
});
}
onMouseDown() {
if (!this.currentPoint) {
const {x, y} = this.cursor;
points.push({ x, y });
resetMovable(points);
this.updateSliders();
redraw();
}
}

View File

@@ -1,87 +0,0 @@
var fit = require('../../../lib/curve-fitter.js');
module.exports = {
setup: function(api) {
this.api = api;
api.noDrag = true; // do not allow points to be dragged around
this.reset();
},
reset: function() {
this.points = [];
this.sliders.setOptions([]);
this.curveset = false;
this.mode = 0;
if (this.api) {
let api = this.api;
api.setCurve(false);
api.reset();
api.redraw();
}
},
toggle: function() {
if (this.api) {
this.customTimeValues = false;
this.mode = (this.mode + 1) % fit.modes.length;
this.fitCurve(this.api);
this.api.redraw();
}
},
draw: function(api, curve) {
api.setPanelCount(1);
api.reset();
api.setColor('lightgrey');
api.drawGrid(10,10);
api.setColor('black');
if (!this.curveset && this.points.length > 2) {
curve = this.fitCurve(api);
}
if (curve) {
api.drawCurve(curve);
api.drawSkeleton(curve);
}
api.drawPoints(this.points);
if (!this.customTimeValues) {
api.setFill(0);
api.text("using "+fit.modes[this.mode]+" t values", {x: 5, y: 10});
}
},
processTimeUpdate(sliderid, timeValues) {
var api = this.api;
this.customTimeValues = true;
this.fitCurve(api, timeValues);
api.redraw();
},
fitCurve(api, timeValues) {
let bestFitData = fit(this.points, timeValues || this.mode),
x = bestFitData.C.x,
y = bestFitData.C.y,
bpoints = [];
x.forEach((r,i) => {
bpoints.push({
x: r[0],
y: y[i][0]
});
});
var curve = new api.Bezier(bpoints);
api.setCurve(curve);
this.curveset = true;
this.sliders.setOptions(bestFitData.S);
return curve;
},
onClick: function(evt, api) {
this.curveset = false;
this.points.push({x: api.mx, y: api.my });
api.redraw();
}
};

View File

@@ -0,0 +1,110 @@
// Copied from http://blog.acipo.com/matrix-inversion-in-javascript/
// Returns the inverse of matrix `M`.
export default function matrix_invert(M) {
// I use Guassian Elimination to calculate the inverse:
// (1) 'augment' the matrix (left) by the identity (on the right)
// (2) Turn the matrix on the left into the identity by elemetry row ops
// (3) The matrix on the right is the inverse (was the identity matrix)
// There are 3 elemtary row ops: (I combine b and c in my code)
// (a) Swap 2 rows
// (b) Multiply a row by a scalar
// (c) Add 2 rows
//if the matrix isn't square: exit (error)
if (M.length !== M[0].length) {
console.log('not square');
return;
}
//create the identity matrix (I), and a copy (C) of the original
var i = 0,
ii = 0,
j = 0,
dim = M.length,
e = 0,
t = 0;
var I = [],
C = [];
for (i = 0; i < dim; i += 1) {
// Create the row
I[I.length] = [];
C[C.length] = [];
for (j = 0; j < dim; j += 1) {
//if we're on the diagonal, put a 1 (for identity)
if (i == j) {
I[i][j] = 1;
} else {
I[i][j] = 0;
}
// Also, make the copy of the original
C[i][j] = M[i][j];
}
}
// Perform elementary row operations
for (i = 0; i < dim; i += 1) {
// get the element e on the diagonal
e = C[i][i];
// if we have a 0 on the diagonal (we'll need to swap with a lower row)
if (e == 0) {
//look through every row below the i'th row
for (ii = i + 1; ii < dim; ii += 1) {
//if the ii'th row has a non-0 in the i'th col
if (C[ii][i] != 0) {
//it would make the diagonal have a non-0 so swap it
for (j = 0; j < dim; j++) {
e = C[i][j]; //temp store i'th row
C[i][j] = C[ii][j]; //replace i'th row by ii'th
C[ii][j] = e; //repace ii'th by temp
e = I[i][j]; //temp store i'th row
I[i][j] = I[ii][j]; //replace i'th row by ii'th
I[ii][j] = e; //repace ii'th by temp
}
//don't bother checking other rows since we've swapped
break;
}
}
//get the new diagonal
e = C[i][i];
//if it's still 0, not invertable (error)
if (e == 0) {
return;
}
}
// Scale this row down by e (so we have a 1 on the diagonal)
for (j = 0; j < dim; j++) {
C[i][j] = C[i][j] / e; //apply to original matrix
I[i][j] = I[i][j] / e; //apply to identity
}
// Subtract this row (scaled appropriately for each row) from ALL of
// the other rows so that there will be 0's in this column in the
// rows above and below this one
for (ii = 0; ii < dim; ii++) {
// Only apply to other rows (we want a 1 on the diagonal)
if (ii == i) {
continue;
}
// We want to change this element to 0
e = C[ii][i];
// Subtract (the row above(or below) scaled by e) from (the
// current row) but start at the i'th column and assume all the
// stuff left of diagonal is 0 (which it should be if we made this
// algorithm correctly)
for (j = 0; j < dim; j++) {
C[ii][j] -= e * C[i][j]; //apply to original matrix
I[ii][j] -= e * I[i][j]; //apply to identity
}
}
}
//we've done all operations, C should be the identity
//matrix I should be the inverse:
return I;
};

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -353,7 +353,7 @@ function Bezier(3,t):
<p>Also shown is the interpolation function for a 15<sup>th</sup> order Bézier function. As you can see, the start and end point contribute considerably more to the curve's shape than any other point in the control point set.</p>
<p>If we want to change the curve, we need to change the weights of each point, effectively changing the interpolations. The way to do this is about as straightforward as possible: just multiply each point with a value that changes its strength. These values are conventionally called "weights", and we can add them to our original Bézier function:</p>
<script>console.log("LaTeX for 14cb9fbbaae9e7d87ae6bef3ea7a782e failed!");</script>
<img class="LaTeX SVG" src="./images/chapters/control/14cb9fbbaae9e7d87ae6bef3ea7a782e.svg" width="379px" height="56px" loading="lazy">
<p>That looks complicated, but as it so happens, the "weights" are actually just the coordinate values we want our curve to have: for an <i>n<sup>th</sup></i> order curve, w<sub>0</sub> is our start coordinate, w<sub>n</sub> is our last coordinate, and everything in between is a controlling coordinate. Say we want a cubic curve that starts at (110,150), is controlled by (25,190) and (210,250) and ends at (210,30), we use this Bézier curve:</p>
<img class="LaTeX SVG" src="./images/chapters/control/c0d4dbc07b8ec7c0a18ea43c8a386935.svg" width="476px" height="40px" loading="lazy">
<p>Which gives us the curve we saw at the top of the article:</p>
@@ -637,7 +637,7 @@ function drawCurve(points[], t):
<section id="matrixsplit">
<h1><a href="#matrixsplit">Splitting curves using matrices</a></h1>
<p>Another way to split curves is to exploit the matrix representation of a Bézier curve. In <a href="#matrix">the section on matrices</a>, we saw that we can represent curves as matrix multiplications. Specifically, we saw these two forms for the quadratic and cubic curves respectively: (we'll reverse the Bézier coefficients vector for legibility)</p>
<script>console.log("LaTeX for 77a11d65d7cffc4b84a85c4bec837792 failed!");</script>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/77a11d65d7cffc4b84a85c4bec837792.svg" width="263px" height="55px" loading="lazy">
<p>and</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/c58330e12d25c678b593ddbd4afa7c52.svg" width="323px" height="73px" loading="lazy">
<p>Let's say we want to split the curve at some point <code>t = z</code>, forming two new (obviously smaller) Bézier curves. To find the coordinates for these two Bézier curves, we can use the matrix representation and some linear algebra. First, we separate out the actual "point on the curve" information into a new matrix multiplication:</p>
@@ -1725,25 +1725,25 @@ for (coordinate, index) in LUT:
<h2>Revisiting the matrix representation</h2>
<p>Rewriting Bézier functions to matrix form is fairly easy, if you first expand the function, and then arrange them into a multiple line form, where each line corresponds to a power of t, and each column is for a specific coefficient. First, we expand the function:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/bd8e8e294eec10d2bf6ef857c7c0c2c2.svg" width="295px" height="43px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/bd8e8e294eec10d2bf6ef857c7c0c2c2.svg" width="307px" height="41px" loading="lazy">
<p>And then we (trivially) rearrange the terms across multiple lines:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2b8334727d3b004c6e87263fec6b32b7.svg" width="216px" height="64px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2b8334727d3b004c6e87263fec6b32b7.svg" width="225px" height="61px" loading="lazy">
<p>This rearrangement has "factors of t" at each row (the first row is t⁰, i.e. "1", the second row is t¹, i.e. "t", the third row is t²) and "coefficient" at each column (the first column is all terms involving "a", the second all terms involving "b", the third all terms involving "c").</p>
<p>With that arrangement, we can easily decompose this as a matrix multiplication:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/94acb5850778dcb16c2ba3cfa676f537.svg" width="572px" height="53px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/94acb5850778dcb16c2ba3cfa676f537.svg" width="592px" height="53px" loading="lazy">
<p>We can do the same for the cubic curve, of course. We know the base function for cubics:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/7eada6f12045423de24d9a2ab8e293b1.svg" width="355px" height="19px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/7eada6f12045423de24d9a2ab8e293b1.svg" width="360px" height="19px" loading="lazy">
<p>So we write out the expansion and rearrange:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/875ca8eea72e727ccb881b4c0b6a3224.svg" width="248px" height="87px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/875ca8eea72e727ccb881b4c0b6a3224.svg" width="255px" height="84px" loading="lazy">
<p>Which we can then decompose:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/03ec73258d5c95eed39a2ea8665e0b07.svg" width="404px" height="72px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/03ec73258d5c95eed39a2ea8665e0b07.svg" width="417px" height="72px" loading="lazy">
<p>And, of course, we can do this for quartic curves too (skipping the expansion step):</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/9151c0fdf9689ee598a2d029ab2ffe34.svg" width="491px" height="92px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/9151c0fdf9689ee598a2d029ab2ffe34.svg" width="505px" height="92px" loading="lazy">
<p>And so and on so on. Now, let's see how to use these <strong>T</strong>, <strong>M</strong>, and <strong>C</strong>, to do some curve fitting.</p>
</div>
<p>Let's get started: we're going to assume we picked the right order curve: for <code>n</code> points we're fitting an <code>n-1</code><sup>th</sup> order curve, so we "start" with a vector <strong>P</strong> that represents the coordinates we already know, and for which we want to do curve fitting:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2bef3da3828d63d690460ce9947dbde2.svg" width="63px" height="73px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2bef3da3828d63d690460ce9947dbde2.svg" width="67px" height="73px" loading="lazy">
<p>Next, we need to figure out appropriate <code>t</code> values for each point in the curve, because we need something that lets us tie "the actual coordinate" to "some point on the curve". There's a fair number of different ways to do this (and a large part of optimizing "the perfect fit" is about picking appropriate <code>t</code> values), but in this case let's look at two "obvious" choices:</p>
<ol>
<li>equally spaced <code>t</code> values, and</li>
@@ -1752,27 +1752,27 @@ for (coordinate, index) in LUT:
<p>The first one is really simple: if we have <code>n</code> points, then we'll just assign each point <code>i</code> a <code>t</code> value of <code>(i-1)/(n-1)</code>. So if we have four points, the first point will have <code>t=(1-1)/(4-1)=0/3</code>, the second point will have <code>t=(2-1)/(4-1)=1/3</code>, the third point will have <code>t=2/3</code>, and the last point will be <code>t=1</code>. We're just straight up spacing the <code>t</code> values to match the number of points we have.</p>
<p>The second one is a little more interesting: since we're doing polynomial regression, we might as well exploit the fact that our base coordinates just constitute a collection of line segments. At the first point, we're fixing t=0, and the last point, we want t=1, and anywhere in between we're simply going to say that <code>t</code> is equal to the distance along the polygon, scaled to the [0,1] domain.</p>
<p>To get these values, we first compute the general "distance along the polygon" matrix:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/78b8ba1aba2e4c9ad3f7890299c90152.svg" width="395px" height="40px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/78b8ba1aba2e4c9ad3f7890299c90152.svg" width="403px" height="40px" loading="lazy">
<p>Where <code>length()</code> is literally just that: the length of the line segment between the point we're looking at, and the previous point. This isn't quite enough, of course: we still need to make sure that all the values between <code>i=1</code> and <code>i=n</code> fall in the [0,1] interval, so we need to scale all values down by whatever the total length of the polygon is:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/08f4beaebf83dca594ad125bdca7e436.svg" width="272px" height="55px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/08f4beaebf83dca594ad125bdca7e436.svg" width="289px" height="55px" loading="lazy">
<p>And now we can move on to the actual "curve fitting" part: what we want is a function that lets us compute "ideal" control point values such that if we build a Bézier curve with them, that curve passes through all our original points. Or, failing that, have an overall error distance that is as close to zero as we can get it. So, let's write out what the error distance looks like.</p>
<p>As mentioned before, this function is really just "the distance between the actual coordinate, and the coordinate that the curve evaluates to for the associated <code>t</code> value", which we'll square to get rid of any pesky negative signs:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/7e5d59272621baf942bc722208ce70c2.svg" width="177px" height="23px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/7e5d59272621baf942bc722208ce70c2.svg" width="184px" height="23px" loading="lazy">
<p>Since this function only deals with individual coordinates, we'll need to sum over all coordinates in order to get the full error function. So, we literally just do that; the total error function is simply the sum of all these individual errors:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/ab334858d3fa309cc1a5ba535a2ca168.svg" width="195px" height="41px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/ab334858d3fa309cc1a5ba535a2ca168.svg" width="204px" height="41px" loading="lazy">
<p>And here's the trick that justifies using matrices: while we can work with individual values using calculus, with matrices we can compute as many values as we make our matrices big, all at the "same time", We can replace the individual terms p<sub>i</sub> with the full <strong>P</strong> coordinate matrix, and we can replace Bézier(s<sub>i</sub>) with the matrix representation <strong>T x M x C</strong> we talked about before, which gives us:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2d42758fba3370f52191306752c2705c.svg" width="141px" height="21px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2d42758fba3370f52191306752c2705c.svg" width="151px" height="23px" loading="lazy">
<p>In which we can replace the rather cumbersome "squaring" operation with a more conventional matrix equivalent:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/5f7fcb86ae1c19612b9fe02e23229e31.svg" width="225px" height="21px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/5f7fcb86ae1c19612b9fe02e23229e31.svg" width="241px" height="23px" loading="lazy">
<p>Here, the letter <code>T</code> is used instead of the number 2, to represent the <a href="https://en.wikipedia.org/wiki/Transpose">matrix transpose</a>; each row in the original matrix becomes a column in the transposed matrix instead (row one becomes column one, row two becomes column two, and so on).</p>
<p>This leaves one problem: <strong>T</strong> isn't actually the matrix we want: we don't want symbolic <code>t</code> values, we want the actual numerical values that we computed for <strong>S</strong>, so we need to form a new matrix, which we'll call 𝕋, that makes use of those, and then use that 𝕋 instead of <strong>T</strong> in our error function:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/6202d7bd150c852b432d807c40fb1647.svg" width="201px" height="96px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/6202d7bd150c852b432d807c40fb1647.svg" width="208px" height="96px" loading="lazy">
<p>Which, because of the first and last values in <strong>S</strong>, means:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/4ffad56e281ee79d0688e93033429f0a.svg" width="212px" height="92px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/4ffad56e281ee79d0688e93033429f0a.svg" width="216px" height="92px" loading="lazy">
<p>Now we can properly write out the error function as matrix operations:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/8d09f2be2c6db79ee966f170ffc25815.svg" width="231px" height="21px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/8d09f2be2c6db79ee966f170ffc25815.svg" width="239px" height="23px" loading="lazy">
<p>So, we have our error function: we now need to figure out the expression for where that function has minimal value, e.g. where the error between the true coordinates and the coordinates generated by the curve fitting is smallest. Like in standard calculus, this requires taking the derivative, and determining where that derivative is zero:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/283bc9e8fe59a78d3c74860f62a66ecb.svg" width="197px" height="36px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/283bc9e8fe59a78d3c74860f62a66ecb.svg" width="200px" height="35px" loading="lazy">
<div class="note">
<h2>Where did this derivative come from?</h2>
@@ -1782,18 +1782,20 @@ for (coordinate, index) in LUT:
</div>
<p>Now, given the above derivative, we can rearrange the terms (following the rules of matrix algebra) so that we end up with an expression for <strong>C</strong>:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/d84d1c71a3ce1918f53eaf8f9fe98ac4.svg" width="168px" height="27px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/d84d1c71a3ce1918f53eaf8f9fe98ac4.svg" width="163px" height="24px" loading="lazy">
<p>Here, the "to the power negative one" is the notation for the <a href="https://en.wikipedia.org/wiki/Invertible_matrix">matrix inverse</a>. But that's all we have to do: we're done. Starting with <strong>P</strong> and inventing some <code>t</code> values based on the polygon the coordinates in <strong>P</strong> define, we can compute the corresponding Bézier coordinates <strong>C</strong> that specify a curve that goes through our points. Or, if it can't go through them exactly, as near as possible.</p>
<p>So before we try that out, how much code is involved in implementing this? Honestly, that answer depends on how much you're going to be writing yourself. If you already have a matrix maths library available, then really not that much code at all. On the other hand, if you are writing this from scratch, you're going to have to write some utility functions for doing your matrix work for you, so it's really anywhere from 50 lines of code to maybe 200 lines of code. Not a bad price to pay for being able to fit curves to prespecified coordinates.</p>
<p>So let's try it out! The following graphic lets you place points, and will start computing exact-fit curves once you've placed at least three. You can click for more points, and the code will simply try to compute an exact fit using a Bézier curve of the appropriate order. Four points? Cubic Bézier. Five points? Quartic. And so on. Of course, this does break down at some point: depending on where you place your points, it might become mighty hard for the fitter to find an exact fit, and things might actually start looking horribly off once you hit 10<sup>th</sup> or higher order curves. But it might not!</p>
<div class="figure">
<Graphic title="Fitting a Bézier curve" setup={this.setup} draw={this.draw} onClick={this.onClick}>
<button onClick={this.toggle} style="position:absolute; right: 0;">toggle</button>
<SliderSet ref={ set => (this.sliders=set) } onChange={this.processTimeUpdate} />
</Graphic>
</div>
<p>So let's try it out! The following graphic lets you place points, and will start computing exact-fit curves once you've placed at least three. You can click for more points, and the code will simply try to compute an exact fit using a Bézier curve of the appropriate order. Four points? Cubic Bézier. Five points? Quartic. And so on. Of course, this does break down at some point: depending on where you place your points, it might become mighty hard for the fitter to find an exact fit, and things might actually start looking horribly off once there's enough points for compound <a href="https://en.wikipedia.org/wiki/Round-off_error#Floating-point_number_system">floating point rounding errors</a> to start making a difference (which is around 10~11 points).</p>
<graphics-element title="Fitting a Bézier curve" width="550" height="275" src="./chapters/curvefitting/curve-fitting.js" >
<fallback-image>
<img width="550px" height="275px" src="images\chapters\curvefitting\c2c92587a184efa6c4fee45e4a3e32ed.png">
Scripts are disabled. Showing fallback image.
</fallback-image>
<button class="toggle">toggle</button>
<div class="sliders"><!-- this will contain range inputs, created by the graphic --></div>
</graphics-element>
<p>You'll note there is a convenient "toggle" buttons that lets you toggle between equidistance <code>t</code> values, and distance ratio along the polygon. 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>
<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>
</section>
<section id="catmullconv">
@@ -2042,7 +2044,7 @@ for (coordinate, index) in LUT:
<p>which we can then substitute in the expression for <em>a</em>:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/ef3ab62bb896019c6157c85aae5d1ed3.svg" width="231px" height="195px" loading="lazy">
<p>A quick check shows that plugging these values for <em>a</em> and <em>b</em> into the expressions for C<sub>x</sub> and C<sub>y</sub> give the same x/y coordinates for both "<em>a</em> away from A" and "<em>b</em> away from B", so let's continue: now that we know the coordinate values for C, we know where our on-curve point T for <em>t=0.5</em> (or angle φ/2) is, because we can just evaluate the Bézier polynomial, and we know where the circle arc's actual point P is for angle φ/2:</p>
<script>console.log("LaTeX for fe32474b4616ee9478e1308308f1b6bf failed!");</script>
<img class="LaTeX SVG" src="./images/chapters/circles/fe32474b4616ee9478e1308308f1b6bf.svg" width="188px" height="32px" loading="lazy">
<p>We compute T, observing that if <em>t=0.5</em>, the polynomial values (1-t)², 2(1-t)t, and t² are 0.25, 0.5, and 0.25 respectively:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/e1059e611aa1e51db41f9ce0b4ebb95a.svg" width="252px" height="35px" loading="lazy">
<p>Which, worked out for the x and y components, gives:</p>

View File

@@ -634,7 +634,7 @@ function drawCurve(points[], t):
<section id="matrixsplit">
<h1><a href="ja-JP/index.html#matrixsplit">行列による曲線の分割</a></h1>
<p>曲線分割には、ベジエ曲線の行列表現を利用する方法もあります。<a href="#matrix">行列についての節</a>では、行列の乗算で曲線が表現できることを確認しました。特に2次・3次のベジエ曲線に関しては、それぞれ以下のような形になりました読みやすさのため、ベジエの係数ベクトルを反転させています</p>
<script>console.log("LaTeX for 77a11d65d7cffc4b84a85c4bec837792 failed!");</script>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/77a11d65d7cffc4b84a85c4bec837792.svg" width="263px" height="55px" loading="lazy">
<p>ならびに</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/c58330e12d25c678b593ddbd4afa7c52.svg" width="323px" height="73px" loading="lazy">
<p>曲線をある点<code>t = z</code>で分割し、新しく2つの自明ですが、より短いベジエ曲線を作ることを考えましょう。曲線の行列表現と線形代数を利用すると、この2つのベジエ曲線の座標を求めることができます。まず、実際の「曲線上の点」の情報を分解し、新しい行列の積のかたちにします。</p>
@@ -1722,25 +1722,25 @@ for (coordinate, index) in LUT:
<h2>Revisiting the matrix representation</h2>
<p>Rewriting Bézier functions to matrix form is fairly easy, if you first expand the function, and then arrange them into a multiple line form, where each line corresponds to a power of t, and each column is for a specific coefficient. First, we expand the function:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/bd8e8e294eec10d2bf6ef857c7c0c2c2.svg" width="295px" height="43px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/bd8e8e294eec10d2bf6ef857c7c0c2c2.svg" width="307px" height="41px" loading="lazy">
<p>And then we (trivially) rearrange the terms across multiple lines:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2b8334727d3b004c6e87263fec6b32b7.svg" width="216px" height="64px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2b8334727d3b004c6e87263fec6b32b7.svg" width="225px" height="61px" loading="lazy">
<p>This rearrangement has "factors of t" at each row (the first row is t⁰, i.e. "1", the second row is t¹, i.e. "t", the third row is t²) and "coefficient" at each column (the first column is all terms involving "a", the second all terms involving "b", the third all terms involving "c").</p>
<p>With that arrangement, we can easily decompose this as a matrix multiplication:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/94acb5850778dcb16c2ba3cfa676f537.svg" width="572px" height="53px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/94acb5850778dcb16c2ba3cfa676f537.svg" width="592px" height="53px" loading="lazy">
<p>We can do the same for the cubic curve, of course. We know the base function for cubics:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/7eada6f12045423de24d9a2ab8e293b1.svg" width="355px" height="19px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/7eada6f12045423de24d9a2ab8e293b1.svg" width="360px" height="19px" loading="lazy">
<p>So we write out the expansion and rearrange:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/875ca8eea72e727ccb881b4c0b6a3224.svg" width="248px" height="87px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/875ca8eea72e727ccb881b4c0b6a3224.svg" width="255px" height="84px" loading="lazy">
<p>Which we can then decompose:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/03ec73258d5c95eed39a2ea8665e0b07.svg" width="404px" height="72px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/03ec73258d5c95eed39a2ea8665e0b07.svg" width="417px" height="72px" loading="lazy">
<p>And, of course, we can do this for quartic curves too (skipping the expansion step):</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/9151c0fdf9689ee598a2d029ab2ffe34.svg" width="491px" height="92px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/9151c0fdf9689ee598a2d029ab2ffe34.svg" width="505px" height="92px" loading="lazy">
<p>And so and on so on. Now, let's see how to use these <strong>T</strong>, <strong>M</strong>, and <strong>C</strong>, to do some curve fitting.</p>
</div>
<p>Let's get started: we're going to assume we picked the right order curve: for <code>n</code> points we're fitting an <code>n-1</code><sup>th</sup> order curve, so we "start" with a vector <strong>P</strong> that represents the coordinates we already know, and for which we want to do curve fitting:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2bef3da3828d63d690460ce9947dbde2.svg" width="63px" height="73px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2bef3da3828d63d690460ce9947dbde2.svg" width="67px" height="73px" loading="lazy">
<p>Next, we need to figure out appropriate <code>t</code> values for each point in the curve, because we need something that lets us tie "the actual coordinate" to "some point on the curve". There's a fair number of different ways to do this (and a large part of optimizing "the perfect fit" is about picking appropriate <code>t</code> values), but in this case let's look at two "obvious" choices:</p>
<ol>
<li>equally spaced <code>t</code> values, and</li>
@@ -1749,27 +1749,27 @@ for (coordinate, index) in LUT:
<p>The first one is really simple: if we have <code>n</code> points, then we'll just assign each point <code>i</code> a <code>t</code> value of <code>(i-1)/(n-1)</code>. So if we have four points, the first point will have <code>t=(1-1)/(4-1)=0/3</code>, the second point will have <code>t=(2-1)/(4-1)=1/3</code>, the third point will have <code>t=2/3</code>, and the last point will be <code>t=1</code>. We're just straight up spacing the <code>t</code> values to match the number of points we have.</p>
<p>The second one is a little more interesting: since we're doing polynomial regression, we might as well exploit the fact that our base coordinates just constitute a collection of line segments. At the first point, we're fixing t=0, and the last point, we want t=1, and anywhere in between we're simply going to say that <code>t</code> is equal to the distance along the polygon, scaled to the [0,1] domain.</p>
<p>To get these values, we first compute the general "distance along the polygon" matrix:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/78b8ba1aba2e4c9ad3f7890299c90152.svg" width="395px" height="40px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/78b8ba1aba2e4c9ad3f7890299c90152.svg" width="403px" height="40px" loading="lazy">
<p>Where <code>length()</code> is literally just that: the length of the line segment between the point we're looking at, and the previous point. This isn't quite enough, of course: we still need to make sure that all the values between <code>i=1</code> and <code>i=n</code> fall in the [0,1] interval, so we need to scale all values down by whatever the total length of the polygon is:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/08f4beaebf83dca594ad125bdca7e436.svg" width="272px" height="55px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/08f4beaebf83dca594ad125bdca7e436.svg" width="289px" height="55px" loading="lazy">
<p>And now we can move on to the actual "curve fitting" part: what we want is a function that lets us compute "ideal" control point values such that if we build a Bézier curve with them, that curve passes through all our original points. Or, failing that, have an overall error distance that is as close to zero as we can get it. So, let's write out what the error distance looks like.</p>
<p>As mentioned before, this function is really just "the distance between the actual coordinate, and the coordinate that the curve evaluates to for the associated <code>t</code> value", which we'll square to get rid of any pesky negative signs:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/7e5d59272621baf942bc722208ce70c2.svg" width="177px" height="23px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/7e5d59272621baf942bc722208ce70c2.svg" width="184px" height="23px" loading="lazy">
<p>Since this function only deals with individual coordinates, we'll need to sum over all coordinates in order to get the full error function. So, we literally just do that; the total error function is simply the sum of all these individual errors:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/ab334858d3fa309cc1a5ba535a2ca168.svg" width="195px" height="41px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/ab334858d3fa309cc1a5ba535a2ca168.svg" width="204px" height="41px" loading="lazy">
<p>And here's the trick that justifies using matrices: while we can work with individual values using calculus, with matrices we can compute as many values as we make our matrices big, all at the "same time", We can replace the individual terms p<sub>i</sub> with the full <strong>P</strong> coordinate matrix, and we can replace Bézier(s<sub>i</sub>) with the matrix representation <strong>T x M x C</strong> we talked about before, which gives us:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2d42758fba3370f52191306752c2705c.svg" width="141px" height="21px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2d42758fba3370f52191306752c2705c.svg" width="151px" height="23px" loading="lazy">
<p>In which we can replace the rather cumbersome "squaring" operation with a more conventional matrix equivalent:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/5f7fcb86ae1c19612b9fe02e23229e31.svg" width="225px" height="21px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/5f7fcb86ae1c19612b9fe02e23229e31.svg" width="241px" height="23px" loading="lazy">
<p>Here, the letter <code>T</code> is used instead of the number 2, to represent the <a href="https://en.wikipedia.org/wiki/Transpose">matrix transpose</a>; each row in the original matrix becomes a column in the transposed matrix instead (row one becomes column one, row two becomes column two, and so on).</p>
<p>This leaves one problem: <strong>T</strong> isn't actually the matrix we want: we don't want symbolic <code>t</code> values, we want the actual numerical values that we computed for <strong>S</strong>, so we need to form a new matrix, which we'll call 𝕋, that makes use of those, and then use that 𝕋 instead of <strong>T</strong> in our error function:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/6202d7bd150c852b432d807c40fb1647.svg" width="201px" height="96px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/6202d7bd150c852b432d807c40fb1647.svg" width="208px" height="96px" loading="lazy">
<p>Which, because of the first and last values in <strong>S</strong>, means:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/4ffad56e281ee79d0688e93033429f0a.svg" width="212px" height="92px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/4ffad56e281ee79d0688e93033429f0a.svg" width="216px" height="92px" loading="lazy">
<p>Now we can properly write out the error function as matrix operations:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/8d09f2be2c6db79ee966f170ffc25815.svg" width="231px" height="21px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/8d09f2be2c6db79ee966f170ffc25815.svg" width="239px" height="23px" loading="lazy">
<p>So, we have our error function: we now need to figure out the expression for where that function has minimal value, e.g. where the error between the true coordinates and the coordinates generated by the curve fitting is smallest. Like in standard calculus, this requires taking the derivative, and determining where that derivative is zero:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/283bc9e8fe59a78d3c74860f62a66ecb.svg" width="197px" height="36px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/283bc9e8fe59a78d3c74860f62a66ecb.svg" width="200px" height="35px" loading="lazy">
<div class="note">
<h2>Where did this derivative come from?</h2>
@@ -1779,18 +1779,20 @@ for (coordinate, index) in LUT:
</div>
<p>Now, given the above derivative, we can rearrange the terms (following the rules of matrix algebra) so that we end up with an expression for <strong>C</strong>:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/d84d1c71a3ce1918f53eaf8f9fe98ac4.svg" width="168px" height="27px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/d84d1c71a3ce1918f53eaf8f9fe98ac4.svg" width="163px" height="24px" loading="lazy">
<p>Here, the "to the power negative one" is the notation for the <a href="https://en.wikipedia.org/wiki/Invertible_matrix">matrix inverse</a>. But that's all we have to do: we're done. Starting with <strong>P</strong> and inventing some <code>t</code> values based on the polygon the coordinates in <strong>P</strong> define, we can compute the corresponding Bézier coordinates <strong>C</strong> that specify a curve that goes through our points. Or, if it can't go through them exactly, as near as possible.</p>
<p>So before we try that out, how much code is involved in implementing this? Honestly, that answer depends on how much you're going to be writing yourself. If you already have a matrix maths library available, then really not that much code at all. On the other hand, if you are writing this from scratch, you're going to have to write some utility functions for doing your matrix work for you, so it's really anywhere from 50 lines of code to maybe 200 lines of code. Not a bad price to pay for being able to fit curves to prespecified coordinates.</p>
<p>So let's try it out! The following graphic lets you place points, and will start computing exact-fit curves once you've placed at least three. You can click for more points, and the code will simply try to compute an exact fit using a Bézier curve of the appropriate order. Four points? Cubic Bézier. Five points? Quartic. And so on. Of course, this does break down at some point: depending on where you place your points, it might become mighty hard for the fitter to find an exact fit, and things might actually start looking horribly off once you hit 10<sup>th</sup> or higher order curves. But it might not!</p>
<div class="figure">
<Graphic title="Fitting a Bézier curve" setup={this.setup} draw={this.draw} onClick={this.onClick}>
<button onClick={this.toggle} style="position:absolute; right: 0;">toggle</button>
<SliderSet ref={ set => (this.sliders=set) } onChange={this.processTimeUpdate} />
</Graphic>
</div>
<p>So let's try it out! The following graphic lets you place points, and will start computing exact-fit curves once you've placed at least three. You can click for more points, and the code will simply try to compute an exact fit using a Bézier curve of the appropriate order. Four points? Cubic Bézier. Five points? Quartic. And so on. Of course, this does break down at some point: depending on where you place your points, it might become mighty hard for the fitter to find an exact fit, and things might actually start looking horribly off once there's enough points for compound <a href="https://en.wikipedia.org/wiki/Round-off_error#Floating-point_number_system">floating point rounding errors</a> to start making a difference (which is around 10~11 points).</p>
<graphics-element title="Fitting a Bézier curve" width="550" height="275" src="./chapters/curvefitting/curve-fitting.js" >
<fallback-image>
<img width="550px" height="275px" src="images\chapters\curvefitting\c2c92587a184efa6c4fee45e4a3e32ed.png">
Scripts are disabled. Showing fallback image.
</fallback-image>
<button class="toggle">toggle</button>
<div class="sliders"><!-- this will contain range inputs, created by the graphic --></div>
</graphics-element>
<p>You'll note there is a convenient "toggle" buttons that lets you toggle between equidistance <code>t</code> values, and distance ratio along the polygon. 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>
<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>
</section>
<section id="catmullconv">
@@ -2039,7 +2041,7 @@ for (coordinate, index) in LUT:
<p>which we can then substitute in the expression for <em>a</em>:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/ef3ab62bb896019c6157c85aae5d1ed3.svg" width="231px" height="195px" loading="lazy">
<p>A quick check shows that plugging these values for <em>a</em> and <em>b</em> into the expressions for C<sub>x</sub> and C<sub>y</sub> give the same x/y coordinates for both "<em>a</em> away from A" and "<em>b</em> away from B", so let's continue: now that we know the coordinate values for C, we know where our on-curve point T for <em>t=0.5</em> (or angle φ/2) is, because we can just evaluate the Bézier polynomial, and we know where the circle arc's actual point P is for angle φ/2:</p>
<script>console.log("LaTeX for fe32474b4616ee9478e1308308f1b6bf failed!");</script>
<img class="LaTeX SVG" src="./images/chapters/circles/fe32474b4616ee9478e1308308f1b6bf.svg" width="188px" height="32px" loading="lazy">
<p>We compute T, observing that if <em>t=0.5</em>, the polynomial values (1-t)², 2(1-t)t, and t² are 0.25, 0.5, and 0.25 respectively:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/e1059e611aa1e51db41f9ce0b4ebb95a.svg" width="252px" height="35px" loading="lazy">
<p>Which, worked out for the x and y components, gives:</p>

View File

@@ -633,11 +633,12 @@ class GraphicsAPI extends BaseAPI {
* convenient grid drawing function
*/
drawGrid(division = 20) {
for (let x = (division / 2) | 0; x < this.width; x += division) {
this.line({ x, y: 0 }, { x, y: this.height });
let w = this.panelWidth ?? this.width;
for (let x = (division / 2) | 0; x < w; x += division) {
this.line(x, 0, x, this.height);
}
for (let y = (division / 2) | 0; y < this.height; y += division) {
this.line({ x: 0, y }, { x: this.width, y });
this.line(0, y, w, y);
}
}

View File

@@ -143,5 +143,6 @@ p code {
}
.slide-control {
display: block;
width: 100%;
}
}

View File

@@ -349,7 +349,7 @@ function Bezier(3,t):
<p>上面有一张是15<sup>th</sup>阶的插值方程。如你所见,在所有控制点中,起点和终点对曲线形状的贡献比其他点更大些。</p>
<p>如果我们要改变曲线,就需要改变每个点的权重,有效地改变插值。可以很直接地做到这个:只要用一个值乘以每个点,来改变它的强度。这个值照惯例称为“权重”,我们可以将它加入我们原始的贝塞尔函数:</p>
<script>console.log("LaTeX for 14cb9fbbaae9e7d87ae6bef3ea7a782e failed!");</script>
<img class="LaTeX SVG" src="./images/chapters/control/14cb9fbbaae9e7d87ae6bef3ea7a782e.svg" width="379px" height="56px" loading="lazy">
<p>看起来很复杂但实际上“权重”只是我们想让曲线所拥有的坐标值对于一条n<sup>th</sup>阶曲线w<sup>0</sup>是起始坐标w<sup>n</sup>是终点坐标中间的所有点都是控制点坐标。假设说一条曲线的起点为120160终点为22040并受点35200和点220260的控制贝塞尔曲线方程就为</p>
<img class="LaTeX SVG" src="./images/chapters/control/c0d4dbc07b8ec7c0a18ea43c8a386935.svg" width="476px" height="40px" loading="lazy">
<p>这就是我们在文章开头看到的曲线:</p>
@@ -628,7 +628,7 @@ function drawCurve(points[], t):
<section id="matrixsplit">
<h1><a href="zh-CN/index.html#matrixsplit">Splitting curves using matrices</a></h1>
<p>Another way to split curves is to exploit the matrix representation of a Bézier curve. In <a href="#matrix">the section on matrices</a>, we saw that we can represent curves as matrix multiplications. Specifically, we saw these two forms for the quadratic and cubic curves respectively: (we'll reverse the Bézier coefficients vector for legibility)</p>
<script>console.log("LaTeX for 77a11d65d7cffc4b84a85c4bec837792 failed!");</script>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/77a11d65d7cffc4b84a85c4bec837792.svg" width="263px" height="55px" loading="lazy">
<p>and</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/c58330e12d25c678b593ddbd4afa7c52.svg" width="323px" height="73px" loading="lazy">
<p>Let's say we want to split the curve at some point <code>t = z</code>, forming two new (obviously smaller) Bézier curves. To find the coordinates for these two Bézier curves, we can use the matrix representation and some linear algebra. First, we separate out the actual "point on the curve" information into a new matrix multiplication:</p>
@@ -1716,25 +1716,25 @@ for (coordinate, index) in LUT:
<h2>Revisiting the matrix representation</h2>
<p>Rewriting Bézier functions to matrix form is fairly easy, if you first expand the function, and then arrange them into a multiple line form, where each line corresponds to a power of t, and each column is for a specific coefficient. First, we expand the function:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/bd8e8e294eec10d2bf6ef857c7c0c2c2.svg" width="295px" height="43px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/bd8e8e294eec10d2bf6ef857c7c0c2c2.svg" width="307px" height="41px" loading="lazy">
<p>And then we (trivially) rearrange the terms across multiple lines:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2b8334727d3b004c6e87263fec6b32b7.svg" width="216px" height="64px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2b8334727d3b004c6e87263fec6b32b7.svg" width="225px" height="61px" loading="lazy">
<p>This rearrangement has "factors of t" at each row (the first row is t⁰, i.e. "1", the second row is t¹, i.e. "t", the third row is t²) and "coefficient" at each column (the first column is all terms involving "a", the second all terms involving "b", the third all terms involving "c").</p>
<p>With that arrangement, we can easily decompose this as a matrix multiplication:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/94acb5850778dcb16c2ba3cfa676f537.svg" width="572px" height="53px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/94acb5850778dcb16c2ba3cfa676f537.svg" width="592px" height="53px" loading="lazy">
<p>We can do the same for the cubic curve, of course. We know the base function for cubics:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/7eada6f12045423de24d9a2ab8e293b1.svg" width="355px" height="19px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/7eada6f12045423de24d9a2ab8e293b1.svg" width="360px" height="19px" loading="lazy">
<p>So we write out the expansion and rearrange:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/875ca8eea72e727ccb881b4c0b6a3224.svg" width="248px" height="87px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/875ca8eea72e727ccb881b4c0b6a3224.svg" width="255px" height="84px" loading="lazy">
<p>Which we can then decompose:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/03ec73258d5c95eed39a2ea8665e0b07.svg" width="404px" height="72px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/03ec73258d5c95eed39a2ea8665e0b07.svg" width="417px" height="72px" loading="lazy">
<p>And, of course, we can do this for quartic curves too (skipping the expansion step):</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/9151c0fdf9689ee598a2d029ab2ffe34.svg" width="491px" height="92px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/9151c0fdf9689ee598a2d029ab2ffe34.svg" width="505px" height="92px" loading="lazy">
<p>And so and on so on. Now, let's see how to use these <strong>T</strong>, <strong>M</strong>, and <strong>C</strong>, to do some curve fitting.</p>
</div>
<p>Let's get started: we're going to assume we picked the right order curve: for <code>n</code> points we're fitting an <code>n-1</code><sup>th</sup> order curve, so we "start" with a vector <strong>P</strong> that represents the coordinates we already know, and for which we want to do curve fitting:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2bef3da3828d63d690460ce9947dbde2.svg" width="63px" height="73px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2bef3da3828d63d690460ce9947dbde2.svg" width="67px" height="73px" loading="lazy">
<p>Next, we need to figure out appropriate <code>t</code> values for each point in the curve, because we need something that lets us tie "the actual coordinate" to "some point on the curve". There's a fair number of different ways to do this (and a large part of optimizing "the perfect fit" is about picking appropriate <code>t</code> values), but in this case let's look at two "obvious" choices:</p>
<ol>
<li>equally spaced <code>t</code> values, and</li>
@@ -1743,27 +1743,27 @@ for (coordinate, index) in LUT:
<p>The first one is really simple: if we have <code>n</code> points, then we'll just assign each point <code>i</code> a <code>t</code> value of <code>(i-1)/(n-1)</code>. So if we have four points, the first point will have <code>t=(1-1)/(4-1)=0/3</code>, the second point will have <code>t=(2-1)/(4-1)=1/3</code>, the third point will have <code>t=2/3</code>, and the last point will be <code>t=1</code>. We're just straight up spacing the <code>t</code> values to match the number of points we have.</p>
<p>The second one is a little more interesting: since we're doing polynomial regression, we might as well exploit the fact that our base coordinates just constitute a collection of line segments. At the first point, we're fixing t=0, and the last point, we want t=1, and anywhere in between we're simply going to say that <code>t</code> is equal to the distance along the polygon, scaled to the [0,1] domain.</p>
<p>To get these values, we first compute the general "distance along the polygon" matrix:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/78b8ba1aba2e4c9ad3f7890299c90152.svg" width="395px" height="40px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/78b8ba1aba2e4c9ad3f7890299c90152.svg" width="403px" height="40px" loading="lazy">
<p>Where <code>length()</code> is literally just that: the length of the line segment between the point we're looking at, and the previous point. This isn't quite enough, of course: we still need to make sure that all the values between <code>i=1</code> and <code>i=n</code> fall in the [0,1] interval, so we need to scale all values down by whatever the total length of the polygon is:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/08f4beaebf83dca594ad125bdca7e436.svg" width="272px" height="55px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/08f4beaebf83dca594ad125bdca7e436.svg" width="289px" height="55px" loading="lazy">
<p>And now we can move on to the actual "curve fitting" part: what we want is a function that lets us compute "ideal" control point values such that if we build a Bézier curve with them, that curve passes through all our original points. Or, failing that, have an overall error distance that is as close to zero as we can get it. So, let's write out what the error distance looks like.</p>
<p>As mentioned before, this function is really just "the distance between the actual coordinate, and the coordinate that the curve evaluates to for the associated <code>t</code> value", which we'll square to get rid of any pesky negative signs:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/7e5d59272621baf942bc722208ce70c2.svg" width="177px" height="23px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/7e5d59272621baf942bc722208ce70c2.svg" width="184px" height="23px" loading="lazy">
<p>Since this function only deals with individual coordinates, we'll need to sum over all coordinates in order to get the full error function. So, we literally just do that; the total error function is simply the sum of all these individual errors:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/ab334858d3fa309cc1a5ba535a2ca168.svg" width="195px" height="41px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/ab334858d3fa309cc1a5ba535a2ca168.svg" width="204px" height="41px" loading="lazy">
<p>And here's the trick that justifies using matrices: while we can work with individual values using calculus, with matrices we can compute as many values as we make our matrices big, all at the "same time", We can replace the individual terms p<sub>i</sub> with the full <strong>P</strong> coordinate matrix, and we can replace Bézier(s<sub>i</sub>) with the matrix representation <strong>T x M x C</strong> we talked about before, which gives us:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2d42758fba3370f52191306752c2705c.svg" width="141px" height="21px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/2d42758fba3370f52191306752c2705c.svg" width="151px" height="23px" loading="lazy">
<p>In which we can replace the rather cumbersome "squaring" operation with a more conventional matrix equivalent:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/5f7fcb86ae1c19612b9fe02e23229e31.svg" width="225px" height="21px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/5f7fcb86ae1c19612b9fe02e23229e31.svg" width="241px" height="23px" loading="lazy">
<p>Here, the letter <code>T</code> is used instead of the number 2, to represent the <a href="https://en.wikipedia.org/wiki/Transpose">matrix transpose</a>; each row in the original matrix becomes a column in the transposed matrix instead (row one becomes column one, row two becomes column two, and so on).</p>
<p>This leaves one problem: <strong>T</strong> isn't actually the matrix we want: we don't want symbolic <code>t</code> values, we want the actual numerical values that we computed for <strong>S</strong>, so we need to form a new matrix, which we'll call 𝕋, that makes use of those, and then use that 𝕋 instead of <strong>T</strong> in our error function:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/6202d7bd150c852b432d807c40fb1647.svg" width="201px" height="96px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/6202d7bd150c852b432d807c40fb1647.svg" width="208px" height="96px" loading="lazy">
<p>Which, because of the first and last values in <strong>S</strong>, means:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/4ffad56e281ee79d0688e93033429f0a.svg" width="212px" height="92px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/4ffad56e281ee79d0688e93033429f0a.svg" width="216px" height="92px" loading="lazy">
<p>Now we can properly write out the error function as matrix operations:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/8d09f2be2c6db79ee966f170ffc25815.svg" width="231px" height="21px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/8d09f2be2c6db79ee966f170ffc25815.svg" width="239px" height="23px" loading="lazy">
<p>So, we have our error function: we now need to figure out the expression for where that function has minimal value, e.g. where the error between the true coordinates and the coordinates generated by the curve fitting is smallest. Like in standard calculus, this requires taking the derivative, and determining where that derivative is zero:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/283bc9e8fe59a78d3c74860f62a66ecb.svg" width="197px" height="36px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/283bc9e8fe59a78d3c74860f62a66ecb.svg" width="200px" height="35px" loading="lazy">
<div class="note">
<h2>Where did this derivative come from?</h2>
@@ -1773,18 +1773,20 @@ for (coordinate, index) in LUT:
</div>
<p>Now, given the above derivative, we can rearrange the terms (following the rules of matrix algebra) so that we end up with an expression for <strong>C</strong>:</p>
<img class="LaTeX SVG" src="./images/chapters/curvefitting/d84d1c71a3ce1918f53eaf8f9fe98ac4.svg" width="168px" height="27px" loading="lazy">
<img class="LaTeX SVG" src="./images/chapters/curvefitting/d84d1c71a3ce1918f53eaf8f9fe98ac4.svg" width="163px" height="24px" loading="lazy">
<p>Here, the "to the power negative one" is the notation for the <a href="https://en.wikipedia.org/wiki/Invertible_matrix">matrix inverse</a>. But that's all we have to do: we're done. Starting with <strong>P</strong> and inventing some <code>t</code> values based on the polygon the coordinates in <strong>P</strong> define, we can compute the corresponding Bézier coordinates <strong>C</strong> that specify a curve that goes through our points. Or, if it can't go through them exactly, as near as possible.</p>
<p>So before we try that out, how much code is involved in implementing this? Honestly, that answer depends on how much you're going to be writing yourself. If you already have a matrix maths library available, then really not that much code at all. On the other hand, if you are writing this from scratch, you're going to have to write some utility functions for doing your matrix work for you, so it's really anywhere from 50 lines of code to maybe 200 lines of code. Not a bad price to pay for being able to fit curves to prespecified coordinates.</p>
<p>So let's try it out! The following graphic lets you place points, and will start computing exact-fit curves once you've placed at least three. You can click for more points, and the code will simply try to compute an exact fit using a Bézier curve of the appropriate order. Four points? Cubic Bézier. Five points? Quartic. And so on. Of course, this does break down at some point: depending on where you place your points, it might become mighty hard for the fitter to find an exact fit, and things might actually start looking horribly off once you hit 10<sup>th</sup> or higher order curves. But it might not!</p>
<div class="figure">
<Graphic title="Fitting a Bézier curve" setup={this.setup} draw={this.draw} onClick={this.onClick}>
<button onClick={this.toggle} style="position:absolute; right: 0;">toggle</button>
<SliderSet ref={ set => (this.sliders=set) } onChange={this.processTimeUpdate} />
</Graphic>
</div>
<p>So let's try it out! The following graphic lets you place points, and will start computing exact-fit curves once you've placed at least three. You can click for more points, and the code will simply try to compute an exact fit using a Bézier curve of the appropriate order. Four points? Cubic Bézier. Five points? Quartic. And so on. Of course, this does break down at some point: depending on where you place your points, it might become mighty hard for the fitter to find an exact fit, and things might actually start looking horribly off once there's enough points for compound <a href="https://en.wikipedia.org/wiki/Round-off_error#Floating-point_number_system">floating point rounding errors</a> to start making a difference (which is around 10~11 points).</p>
<graphics-element title="Fitting a Bézier curve" width="550" height="275" src="./chapters/curvefitting/curve-fitting.js" >
<fallback-image>
<img width="550px" height="275px" src="images\chapters\curvefitting\c2c92587a184efa6c4fee45e4a3e32ed.png">
Scripts are disabled. Showing fallback image.
</fallback-image>
<button class="toggle">toggle</button>
<div class="sliders"><!-- this will contain range inputs, created by the graphic --></div>
</graphics-element>
<p>You'll note there is a convenient "toggle" buttons that lets you toggle between equidistance <code>t</code> values, and distance ratio along the polygon. 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>
<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>
</section>
<section id="catmullconv">
@@ -2033,7 +2035,7 @@ for (coordinate, index) in LUT:
<p>which we can then substitute in the expression for <em>a</em>:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/ef3ab62bb896019c6157c85aae5d1ed3.svg" width="231px" height="195px" loading="lazy">
<p>A quick check shows that plugging these values for <em>a</em> and <em>b</em> into the expressions for C<sub>x</sub> and C<sub>y</sub> give the same x/y coordinates for both "<em>a</em> away from A" and "<em>b</em> away from B", so let's continue: now that we know the coordinate values for C, we know where our on-curve point T for <em>t=0.5</em> (or angle φ/2) is, because we can just evaluate the Bézier polynomial, and we know where the circle arc's actual point P is for angle φ/2:</p>
<script>console.log("LaTeX for fe32474b4616ee9478e1308308f1b6bf failed!");</script>
<img class="LaTeX SVG" src="./images/chapters/circles/fe32474b4616ee9478e1308308f1b6bf.svg" width="188px" height="32px" loading="lazy">
<p>We compute T, observing that if <em>t=0.5</em>, the polynomial values (1-t)², 2(1-t)t, and t² are 0.25, 0.5, and 0.25 respectively:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/e1059e611aa1e51db41f9ce0b4ebb95a.svg" width="252px" height="35px" loading="lazy">
<p>Which, worked out for the x and y components, gives:</p>