Least-squares curve with given knots
64-bitThe curve with given knots that is closest to a set of points, with its ends on the first and last point.
(NᵀN)·P = NᵀR, R = Q − N₀·Q₀ − Nₙ·Q(last)
The normal equations are built by adding products over the samples and solved by Gaussian elimination; forming NᵀN squares the condition number of the fit. A control point that no sample reaches (diagonal below 1e-12) gets 2e-6 added on the diagonal and −1e-6 toward each neighbor, which ties it to them.
export function fitFixedKnots(Q, t, p, U, { fixEnds = true, weights = null } = {}) {
const n = U.length - p - 2, m = Q.length - 1;
const P = new Array(n + 1);
if (fixEnds) { P[0] = Q[0].slice(0, 3); P[n] = Q[m].slice(0, 3); }
const lo = fixEnds ? 1 : 0, hi = fixEnds ? n - 1 : n, k = hi - lo + 1;
if (k <= 0) return { deg: p, knots: U.slice(), cvs: P.map((q, i) => q || Q[Math.round(i * m / n)].slice(0, 3)), w: null };
const NtN = Array.from({ length: k }, () => new Array(k).fill(0)), NtR = Array.from({ length: k }, () => [0, 0, 0]);
for (let i = 0; i <= m; i++) {
if (fixEnds && (i === 0 || i === m)) continue;
const row = rowAt(p, U, n, t[i]), wt = weights ? weights[i] : 1;
let r = Q[i].slice(0, 3);
if (fixEnds) r = V.sub(V.sub(r, V.mul(P[0], row[0])), V.mul(P[n], row[n]));
for (let a = lo; a <= hi; a++) {
if (!row[a]) continue;
for (let b = lo; b <= hi; b++) if (row[b]) NtN[a - lo][b - lo] += wt * row[a] * row[b];
for (let d = 0; d < 3; d++) NtR[a - lo][d] += wt * row[a] * r[d];
}
}
// a control point no sample reaches would leave the system singular: tie it lightly to its neighbours (smoothing)
for (let a = 0; a < k; a++) if (NtN[a][a] < 1e-12) {
NtN[a][a] += 2e-6;
if (a > 0) NtN[a][a - 1] -= 1e-6;
if (a + 1 < k) NtN[a][a + 1] -= 1e-6;
}
const X = V.solve(NtN, NtR);
if (!X) fail('nurbs.err.fit_singular');
for (let a = lo; a <= hi; a++) P[a] = X[a - lo];
return { deg: p, knots: U.slice(), cvs: P, w: null };
}