Resources Numerical accuracy Calculation policy

Knot vectors

Builds and checks the knot vectors that NURBS curves and surfaces are defined on.

How the precision is kept

Knots are 64-bit numbers. Two knots are treated as one when they differ by no more than 1e-12 of the whole knot range, a relative tolerance. The clamping check compares end knots for exact equality. Binomial coefficients are built by whole-number addition.

Calculations under this policy

6 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

nurbs/basis.js ModelCraft 0.8.1 64-bit 6 calculations Reviewed

Tests tools/nurbs/test.mjs

Multiplicity of a knot

64-bit

How many times a value occurs in a knot vector.

count of |Uᵢ − u| ≤ tol × (U_last − U_first)

The tolerance is 1e-12 by default, scaled by the knot range, so the result does not depend on how the curve is parameterized.

  • nurbs/basis.js
  • lines 93 to 98
  • ModelCraft 0.8.1
export function multiplicity(U, u, tol = KTOL) {
  const span = Math.max(1e-300, U[U.length - 1] - U[0]);
  let m = 0;
  for (const x of U) if (Math.abs(x - u) <= tol * span) m++;
  return m;
}

Distinct knots

64-bit

The different knot values with their multiplicities.

a knot joins the previous one when |x − u_prev| ≤ tol × range

Each knot is compared with the first value of the current group, not with its neighbor, so a run of slowly rising knots cannot chain beyond the tolerance. The group keeps its first value.

  • nurbs/basis.js
  • lines 100 to 109
  • ModelCraft 0.8.1
export function distinctKnots(U, tol = KTOL) {
  const span = Math.max(1e-300, U[U.length - 1] - U[0]);
  const out = [];
  for (const x of U) {
    const last = out[out.length - 1];
    if (last && Math.abs(x - last.u) <= tol * span) last.m++;
    else out.push({ u: x, m: 1 });
  }
  return out;
}

Clamped uniform knot vector

64-bit

A knot vector with evenly spaced interior knots.

uᵢ = a + (b − a) × i ÷ (inner + 1)

Each interior knot is computed from its index, not by repeated addition of a step. The end knots are a and b themselves.

  • nurbs/basis.js
  • lines 111 to 119
  • ModelCraft 0.8.1
/** A clamped knot vector with uniform interior knots for `count` control points of degree p on [a, b]. */
export function clampedUniform(count, p, a = 0, b = 1) {
  const U = [];
  for (let i = 0; i <= p; i++) U.push(a);
  const inner = count - p - 1;
  for (let i = 1; i <= inner; i++) U.push(a + (b - a) * i / (inner + 1));
  for (let i = 0; i <= p; i++) U.push(b);
  return U;
}

Knot vector check

64-bit

Checks a knot vector read from a file: count, order, clamping and multiplicities.

Order is checked with plain comparisons and clamping with exact equality of the first and last p + 1 knots. Interior multiplicities use the relative 1e-12 grouping of distinctKnots.

  • nurbs/basis.js
  • lines 121 to 131
  • ModelCraft 0.8.1
export function knotProblem(U, p, count) {
  if (!Array.isArray(U) || U.length !== count + p + 1) return 'nurbs.err.knots_count';
  for (const x of U) if (typeof x !== 'number' || !Number.isFinite(x)) return 'nurbs.err.knots_value';
  for (let i = 1; i < U.length; i++) if (U[i] < U[i - 1]) return 'nurbs.err.knots_order';
  const a = U[0], b = U[U.length - 1];
  if (!(b > a)) return 'nurbs.err.knots_value';
  for (let i = 0; i <= p; i++) if (U[i] !== a || U[U.length - 1 - i] !== b) return 'nurbs.err.knots_clamp';
  // an interior knot may repeat at most p times (p + 1 would break the curve in two)
  for (const k of distinctKnots(U)) if (k.u !== a && k.u !== b && k.m > p) return 'nurbs.err.knots_mult';
  return null;
}

Greville abscissae

64-bit

One parameter value per control point.

gᵢ = (Uᵢ₊₁ + … + Uᵢ₊ₚ) ÷ p

p knots are added in order and divided once. For degree 0 the mean of two knots is used.

  • nurbs/basis.js
  • lines 133 to 137
  • ModelCraft 0.8.1
export function greville(U, p, count) {
  const out = new Array(count);
  for (let i = 0; i < count; i++) { let s = 0; for (let k = 1; k <= p; k++) s += U[i + k]; out[i] = p ? s / p : (U[i] + U[i + 1]) / 2; }
  return out;
}

Binomial coefficients

Exact

The number of ways to choose k of n.

C(n, k) = C(n − 1, k − 1) + C(n − 1, k)

Rows of Pascal's triangle are built by adding whole numbers and kept in a table. The values are exact up to 2⁵³; the file does not check for that bound.

  • nurbs/basis.js
  • lines 140 to 148
  • ModelCraft 0.8.1
export function binomial(n, k) {
  for (let r = BIN.length; r <= n; r++) {
    const row = [1];
    for (let j = 1; j < r; j++) row.push(BIN[r - 1][j - 1] + BIN[r - 1][j]);
    row.push(1);
    BIN.push(row);
  }
  return k < 0 || k > n ? 0 : BIN[n][k];
}

In detail

Method
The file's header cites Piegl and Tiller, The NURBS Book, for its conventions. Greville abscissae are averages of p consecutive knots.
Number types
64-bit numbers in plain arrays.
Tolerances
KTOL = 1e-12, multiplied by the knot range (the range is floored at 1e-300).
Limits
Binomial coefficients are exact while they stay below 2⁵³.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach