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

catmull-rom

This commit is contained in:
Pomax
2020-09-02 16:39:10 -07:00
parent 4aef67e662
commit bb5adcaebd
42 changed files with 979 additions and 630 deletions

View File

@@ -0,0 +1,21 @@
var binomialCoefficients = [[1], [1, 1]];
/**
* ... docs go here ...
*/
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];
}
export default binomial;

View File

@@ -0,0 +1,86 @@
import { Matrix } from "../types/matrix.js";
import binomial from "./binomial.js";
/*
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:
*/
function generateBasisMatrix(n) {
const M = new Matrix(n, n);
// populate the main diagonal
var k = n - 1;
for (let i = 0; i < n; i++) {
M.set(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 === 0 ? 1 : -1;
var value = binomial(r, c) * M.get(r, r);
M.set(r, c, sign * value);
}
}
return M;
}
/**
* ...docs go here...
*/
function formTMatrix(row, n) {
let data = [];
for (var i = 0; i < n; i++) {
data.push(row.map((v) => v ** i));
}
const Tt = new Matrix(n, n, data);
const T = Tt.transpose();
return { T, Tt };
}
/**
* ...docs go here...
*/
function computeBestFit(points, n, M, S) {
var tm = formTMatrix(S, n),
T = tm.T,
Tt = tm.Tt,
M1 = M.invert(),
TtT1 = Tt.multiply(T).invert(),
step1 = TtT1.multiply(Tt),
step2 = M1.multiply(step1),
X = new Matrix(points.map((v) => [v.x])),
Cx = step2.multiply(X),
Y = new Matrix(points.map((v) => [v.y])),
Cy = step2.multiply(Y);
return { x: Cx.data, y: Cy.data };
}
/**
* ...docs go here...
*/
function fitCurveToPoints(points, tvalues) {
const n = points.length;
return computeBestFit(points, n, generateBasisMatrix(n), tvalues);
}
export { fitCurveToPoints };

View File

@@ -1,142 +0,0 @@
// Copied from http://blog.acipo.com/matrix-inversion-in-javascript/
function 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;
}
function multiply(m1, m2) {
var M = [];
var m2t = transpose(m2);
m1.forEach((row, r) => {
M[r] = [];
m2t.forEach((col, c) => {
M[r][c] = row.map((v, i) => col[i] * v).reduce((a, v) => a + v, 0);
});
});
return M;
}
function transpose(M) {
return M[0].map((col, i) => M.map((row) => row[i]));
}
class Matrix {
constructor(data) {
this.data = data;
}
multiply(other) {
return new Matrix(multiply(this.data, other.data));
}
invert() {
return new Matrix(invert(this.data));
}
transpose() {
return new Matrix(transpose(this.data));
}
}
export { Matrix };