1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-28 18:49:57 +02:00

renamed graphics-element dir

This commit is contained in:
Pomax
2020-11-06 11:32:44 -08:00
parent 3288732350
commit 77284e1051
34 changed files with 25 additions and 25 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

@@ -0,0 +1,88 @@
// https://github.com/thibauts/b-spline
export default function interpolate(t, degree, points, knots, weights, result, scaled) {
var i, j, s, l; // function-scoped iteration variables
var n = points.length; // points count
var d = points[0].length; // point dimensionality
if (degree < 1) throw new Error("degree must be at least 1 (linear)");
if (degree > n - 1) throw new Error("degree must be less than or equal to point count - 1");
if (!weights) {
// build weight vector of length [n]
weights = [];
for (i = 0; i < n; i++) {
weights[i] = 1;
}
}
// closed curve?
if (weights.length < points.length) {
weights = weights.concat(weights.slice(0, degree));
}
if (!knots) {
// build knot vector of length [n + degree + 1]
var knots = [];
for (i = 0; i < n + degree + 1; i++) {
knots[i] = i;
}
} else {
if (knots.length !== n + degree + 1) throw new Error("bad knot vector length");
}
// closed curve?
if (knots.length === points.length) {
knots = knots.concat(knots.slice(0, degree));
}
var domain = [degree, knots.length - 1 - degree];
var low = knots[domain[0]];
var high = knots[domain[1]];
// remap t to the domain where the spline is defined
if (!scaled) {
t = t * (high - low) + low;
}
if (t < low || t > high) throw new Error("out of bounds");
// find s (the spline segment) for the [t] value provided
for (s = domain[0]; s < domain[1]; s++) {
if (t >= knots[s] && t <= knots[s + 1]) {
break;
}
}
// convert points to homogeneous coordinates
var v = [];
for (i = 0; i < n; i++) {
v[i] = [];
for (j = 0; j < d; j++) {
v[i][j] = points[i][j] * weights[i];
}
v[i][d] = weights[i];
}
// l (level) goes from 1 to the curve degree + 1
var alpha;
for (l = 1; l <= degree + 1; l++) {
// build level l of the pyramid
for (i = s; i > s - degree - 1 + l; i--) {
alpha = (t - knots[i]) / (knots[i + degree + 1 - l] - knots[i]);
// interpolate each component
for (j = 0; j < d + 1; j++) {
v[i][j] = (1 - alpha) * v[i - 1][j] + alpha * v[i][j];
}
}
}
// convert back to cartesian and return
var result = result || [];
for (i = 0; i < d; i++) {
result[i] = v[s][i] / v[s][d];
}
return result;
}

View File

@@ -0,0 +1,61 @@
/**
* A complex shape, represented as a collection of paths
* that can be either polygon, Catmull-Rom curves, or
* cubic Bezier curves.
*/
class Shape {
constructor(type, factor, points = []) {
this.first = false;
this.segments = [];
this.newSegment(type, factor);
points.forEach((p) => this.vertex(p));
}
merge(other) {
if (!other.segments) {
other = { segments: [new Segment(Shape.POLYGON, undefined, other)] };
}
other.segments.forEach((s) => this.segments.push(s));
}
copy() {
const copy = new Shape(this.type, this.factor);
copy.first = this.first;
copy.segments = this.segments.map((s) => s.copy());
return copy;
}
newSegment(type, factor) {
this.currentSegment = new Segment(type, factor);
this.segments.push(this.currentSegment);
}
vertex(p) {
if (!this.first) this.first = p;
else this.currentSegment.add(p);
}
}
/**
* Pathing type constants
*/
Shape.POLYGON = `Polygon`;
Shape.CURVE = `CatmullRom`;
Shape.BEZIER = `Bezier`;
/**
* A shape subpath
*/
class Segment {
constructor(type, factor, points = []) {
this.type = type;
this.factor = factor;
this.points = points;
}
copy() {
const copy = new Segment(this.type, this.factor);
copy.points = JSON.parse(JSON.stringify(this.points));
return copy;
}
add(p) {
this.points.push(p);
}
}
export { Shape, Segment };