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:
21
docs/js/graphics-element/api/util/binomial.js
Normal file
21
docs/js/graphics-element/api/util/binomial.js
Normal 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;
|
86
docs/js/graphics-element/api/util/fit-curve-to-points.js
Normal file
86
docs/js/graphics-element/api/util/fit-curve-to-points.js
Normal 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 };
|
88
docs/js/graphics-element/api/util/interpolate-bspline.js
Normal file
88
docs/js/graphics-element/api/util/interpolate-bspline.js
Normal 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;
|
||||
}
|
61
docs/js/graphics-element/api/util/shape.js
Normal file
61
docs/js/graphics-element/api/util/shape.js
Normal 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 };
|
Reference in New Issue
Block a user