1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-09-28 09:09: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

@@ -1,8 +1,9 @@
import { enrich } from "../lib/enrich.js";
import { Bezier } from "./types/bezier.js";
import { Vector } from "./types/vector.js";
import { Matrix } from "./types/matrix.js";
import { Shape } from "./util/shape.js";
import { Matrix } from "./util/matrix.js";
import binomial from "./util/binomial.js";
import { BaseAPI } from "./base-api.js";
const MOUSE_PRECISION_ZONE = 5;
@@ -169,12 +170,18 @@ class GraphicsAPI extends BaseAPI {
return undefined;
}
slider.value = initial;
this[propname] = parseFloat(slider.value);
let handlerName = `on${propname[0].toUpperCase()}${propname
.substring(1)
.toLowerCase()}`;
if (this[handlerName]) {
this[handlerName](initial);
} else {
slider.value = initial;
}
slider.listen(`input`, (evt) => {
this[propname] = parseFloat(evt.target.value);
if (this[handlerName]) this[handlerName](this[propname]);
@@ -725,6 +732,10 @@ class GraphicsAPI extends BaseAPI {
return Math.pow(v, p);
}
binomial(n, k) {
return binomial(n, k);
}
map(v, s, e, ns, ne, constrain = false) {
const i1 = e - s,
i2 = ne - ns,

View File

@@ -1,5 +1,6 @@
import { Vector } from "./vector.js";
import { Bezier as Original } from "../../lib/bezierjs/bezier.js";
import { fitCurveToPoints } from "../util/fit-curve-to-points.js";
/**
* A canvas-aware Bezier curve class
@@ -23,6 +24,30 @@ class Bezier extends Original {
return new Bezier(apiInstance, 110, 150, 25, 190, 210, 250, 210, 30);
}
static fitCurveToPoints(apiInstance, points, tvalues) {
if (!tvalues) {
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;
});
tvalues = S;
}
const bestFitData = fitCurveToPoints(points, tvalues),
x = bestFitData.x,
y = bestFitData.y,
bpoints = x.map((r, i) => ({ x: r[0], y: y[i][0] }));
return new Bezier(apiInstance, bpoints);
}
constructor(apiInstance, ...coords) {
if (!apiInstance || !apiInstance.setMovable) {
throw new Error(

View File

@@ -125,9 +125,33 @@ function transpose(M) {
}
class Matrix {
constructor(data) {
constructor(n, m, data) {
data = n instanceof Array ? n : data;
this.data =
data ?? [...new Array(n)].map((v) => [...new Array(m)].map((v) => 0));
this.rows = this.data.length;
this.cols = this.data[0].length;
}
setData(data) {
this.data = data;
}
get(i, j) {
return this.data[i][j];
}
set(i, j, value) {
this.data[i][j] = value;
}
row(i) {
return this.data[i];
}
col(i) {
var d = this.data,
col = [];
for (let r = 0, l = d.length; r < l; r++) {
col.push(d[r][i]);
}
return col;
}
multiply(other) {
return new Matrix(multiply(this.data, other.data));
}

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 };