UNPKG

react-three-nurbs

Version:

A React component library for NURBS (Non-Uniform Rational B-Spline) curves, surfaces, and solids in Three.js. Built with React Three Fiber, zero external NURBS dependencies — all math implemented from scratch. Boolean operations powered by OpenCASCADE WAS

1,528 lines 105 kB
var en = Object.defineProperty; var rn = (n, t, e) => t in n ? en(n, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[t] = e; var Q = (n, t, e) => rn(n, typeof t != "symbol" ? t + "" : t, e); import { jsx as D, jsxs as Zt } from "react/jsx-runtime"; import { useMemo as j, isValidElement as lt, forwardRef as Nt, Children as et, useRef as z, useState as jt, useEffect as on, useCallback as X } from "react"; import { Vector3 as U, BufferGeometry as tt, Float32BufferAttribute as F, DoubleSide as rt, Plane as sn, Raycaster as cn, Vector2 as ln } from "three"; import { Line as it } from "@react-three/drei"; import { useThree as un } from "@react-three/fiber"; function st(n, t, e, r) { if (e >= r[n + 1]) return n; if (e <= r[t]) return t; let s = t, o = n + 1, c = s + o >> 1; for (; e < r[c] || e >= r[c + 1]; ) e < r[c] ? o = c : s = c, c = s + o >> 1; return c; } function gt(n, t, e, r) { const s = new Array(e + 1), o = new Array(e + 1), c = new Array(e + 1); s[0] = 1; for (let l = 1; l <= e; l++) { o[l] = t - r[n + 1 - l], c[l] = r[n + l] - t; let h = 0; for (let f = 0; f < l; f++) { const p = s[f] / (c[f + 1] + o[l - f]); s[f] = h + c[f + 1] * p, h = o[l - f] * p; } s[l] = h; } return s; } function Kt(n, t, e, r, s) { const o = Array.from( { length: e + 1 }, () => new Array(e + 1).fill(0) ), c = new Array(e + 1), l = new Array(e + 1); o[0][0] = 1; for (let a = 1; a <= e; a++) { c[a] = t - s[n + 1 - a], l[a] = s[n + a] - t; let i = 0; for (let u = 0; u < a; u++) { o[a][u] = l[u + 1] + c[a - u]; const g = o[u][a - 1] / o[a][u]; o[u][a] = i + l[u + 1] * g, i = c[a - u] * g; } o[a][a] = i; } const h = Array.from( { length: r + 1 }, () => new Array(e + 1).fill(0) ); for (let a = 0; a <= e; a++) h[0][a] = o[a][e]; const f = [new Array(e + 1).fill(0), new Array(e + 1).fill(0)]; for (let a = 0; a <= e; a++) { let i = 0, u = 1; f[0][0] = 1; for (let g = 1; g <= r; g++) { let m = 0; const y = a - g, w = e - g; a >= g && (f[u][0] = f[i][0] / o[w + 1][y], m = f[u][0] * o[y][w]); const v = y >= -1 ? 1 : -y, x = a - 1 <= w ? g - 1 : e - a; for (let P = v; P <= x; P++) f[u][P] = (f[i][P] - f[i][P - 1]) / o[w + 1][y + P], m += f[u][P] * o[y + P][w]; a <= w && (f[u][g] = -f[i][g - 1] / o[w + 1][a], m += f[u][g] * o[a][w]), h[g][a] = m; const d = i; i = u, u = d; } } let p = e; for (let a = 1; a <= r; a++) { for (let i = 0; i <= e; i++) h[a][i] *= p; p *= e - a; } return h; } function Tt(n, t, e) { let r = n; for (let s = 0; s < e; s++) r = an(r, t); return r; } function an(n, t) { const { degree: e, knots: r, controlPoints: s, weights: o } = n, c = s.length - 1, l = s[0].length; let h = e; for (let i = 0; i < r.length - 1; i++) if (r[i] <= t && t < r[i + 1]) { h = i; break; } t >= r[r.length - e - 1] && (h = r.length - e - 2); const f = [...r.slice(0, h + 1), t, ...r.slice(h + 1)], p = new Array(c + 2), a = new Array(c + 2); for (let i = 0; i <= c + 1; i++) if (i <= h - e) p[i] = [...s[i]], a[i] = o[i]; else if (i >= h + 1) p[i] = [...s[i - 1]], a[i] = o[i - 1]; else { const u = (t - r[i]) / (r[i + e] - r[i]), g = o[i - 1], m = o[i], y = (1 - u) * g + u * m, w = new Array(l); for (let v = 0; v < l; v++) w[v] = ((1 - u) * g * s[i - 1][v] + u * m * s[i][v]) / y; p[i] = w, a[i] = y; } return { degree: e, knots: f, controlPoints: p, weights: a }; } function Vt(n, t) { const e = [...t].sort((s, o) => s - o); let r = n; for (const s of e) r = Tt(r, s, 1); return r; } function fn(n, t, e) { const { degreeU: r, degreeV: s, knotsU: o, knotsV: c, controlPoints: l, weights: h } = n, f = l.length, p = l[0].length; if (e) { const a = []; for (let i = 0; i < f; i++) { const u = { degree: s, knots: [...c], controlPoints: l[i].map((m) => [...m]), weights: [...h[i]] }, g = Vt(u, t); a.push({ cp: g.controlPoints, w: g.weights, knots: g.knots }); } return { degreeU: r, degreeV: s, knotsU: [...o], knotsV: a[0].knots, controlPoints: a.map((i) => i.cp), weights: a.map((i) => i.w) }; } else { const a = [], i = [], u = []; for (let w = 0; w < p; w++) { const v = { degree: r, knots: [...o], controlPoints: Array.from({ length: f }, (d, P) => [...l[P][w]]), weights: Array.from({ length: f }, (d, P) => h[P][w]) }, x = Vt(v, t); w === 0 && a.push(...x.knots), i.push(x.controlPoints.map((d) => [d])), u.push(x.weights); } const g = i[0].length, m = [], y = []; for (let w = 0; w < g; w++) { m[w] = [], y[w] = []; for (let v = 0; v < p; v++) m[w][v] = i[v][w][0], y[w][v] = u[v][w]; } return { degreeU: r, degreeV: s, knotsU: a, knotsV: [...c], controlPoints: m, weights: y }; } } function Ot(n, t) { if (t <= n.degree) return { ...n }; const e = t - n.degree; let r = n; for (let s = 0; s < e; s++) r = hn(r); return r; } function hn(n) { const { degree: t, knots: e, controlPoints: r, weights: s } = n, o = r.length - 1, c = r[0].length, l = t + 1, h = r.map((y, w) => { const v = s[w]; return [...y.map((x) => x * v), v]; }), f = []; let p = 0; for (; p < e.length; ) { const y = e[p]; let w = 0; for (; p < e.length && Math.abs(e[p] - y) < 1e-14; ) w++, p++; f.push({ value: y, mult: w }); } const a = []; for (const y of f) for (let w = 0; w < y.mult + 1; w++) a.push(y.value); const i = a.length - l - 2, u = new Array(i + 1); for (let y = 0; y <= i; y++) { const w = y <= 0 ? 0 : y > o ? 1 : y / l, v = Math.max(0, Math.min(o, y - 1)), x = Math.max(0, Math.min(o, y)); u[y] = new Array(c + 1); for (let d = 0; d <= c; d++) u[y][d] = w * h[v][d] + (1 - w) * h[x][d]; } const g = u.map((y) => { const w = y[c]; return y.slice(0, c).map((v) => w !== 0 ? v / w : v); }), m = u.map((y) => y[c]); return { degree: l, knots: a, controlPoints: g, weights: m }; } function Ht(n) { if (n.length === 0) return []; if (n.length === 1) return [{ ...n[0] }]; const t = Math.max(...n.map((s) => s.degree)); let e = n.map( (s) => s.degree < t ? Ot(s, t) : { ...s } ); const r = /* @__PURE__ */ new Set(); for (const s of e) for (let o = t + 1; o < s.knots.length - t - 1; o++) r.add(s.knots[o]); return e = e.map((s) => { const o = []; for (const c of r) s.knots.some((h) => Math.abs(h - c) < 1e-14) || o.push(c); return o.length > 0 ? Vt(s, o) : s; }), e; } function Gt(n, t) { const e = n.length - 1, r = n[0].length, s = Math.min(t, e), o = new Array(e + 1); o[0] = 0; const c = new Array(e); let l = 0; for (let i = 1; i <= e; i++) { let u = 0; for (let g = 0; g < r; g++) u += (n[i][g] - n[i - 1][g]) ** 2; c[i - 1] = Math.sqrt(u), l += c[i - 1]; } l === 0 && (l = 1); for (let i = 1; i <= e; i++) o[i] = o[i - 1] + c[i - 1] / l; o[e] = 1; const h = e + s + 1, f = new Array(h + 1); for (let i = 0; i <= s; i++) f[i] = 0; for (let i = h - s; i <= h; i++) f[i] = 1; for (let i = 1; i <= e - s; i++) { let u = 0; for (let g = i; g <= i + s - 1; g++) u += o[g]; f[i + s] = u / s; } const p = Array.from({ length: e + 1 }, () => new Array(e + 1).fill(0)); for (let i = 0; i <= e; i++) { const u = st(e, s, o[i], f), g = gt(u, o[i], s, f); for (let m = 0; m <= s; m++) p[i][u - s + m] = g[m]; } const a = new Array(e + 1); for (let i = 0; i <= e; i++) a[i] = new Array(r); for (let i = 0; i < r; i++) { const u = n.map((m) => m[i]), g = pn(p, u); for (let m = 0; m <= e; m++) a[m][i] = g[m]; } return { degree: s, knots: f, controlPoints: a, weights: new Array(e + 1).fill(1) }; } function gn(n, t) { if (n.length < 2) throw new Error("Lofting requires at least 2 curves"); const e = Ht(n), r = e[0].degree, s = e[0].knots, o = e[0].controlPoints.length, c = e.length, l = Math.min(t, c - 1), h = []; for (let g = 0; g < o; g++) { const m = []; for (let y = 0; y < c; y++) m.push(e[y].controlPoints[g]); h.push(m); } const f = h.map( (g) => Gt(g, l) ), p = f[0].knots, a = f[0].controlPoints.length, i = [], u = []; for (let g = 0; g < o; g++) { i[g] = [], u[g] = []; for (let m = 0; m < a; m++) i[g][m] = f[g].controlPoints[m], u[g][m] = 1; } return { degreeU: r, degreeV: l, knotsU: s, knotsV: p, controlPoints: i, weights: u }; } function pn(n, t) { const e = t.length, r = n.map((o, c) => [...o, t[c]]); for (let o = 0; o < e; o++) { let c = o, l = Math.abs(r[o][o]); for (let f = o + 1; f < e; f++) Math.abs(r[f][o]) > l && (l = Math.abs(r[f][o]), c = f); c !== o && ([r[o], r[c]] = [r[c], r[o]]); const h = r[o][o]; if (!(Math.abs(h) < 1e-14)) for (let f = o + 1; f < e; f++) { const p = r[f][o] / h; for (let a = o; a <= e; a++) r[f][a] -= p * r[o][a]; } } const s = new Array(e).fill(0); for (let o = e - 1; o >= 0; o--) { let c = r[o][e]; for (let l = o + 1; l < e; l++) c -= r[o][l] * s[l]; s[o] = Math.abs(r[o][o]) > 1e-14 ? c / r[o][o] : 0; } return s; } let L = class ut { constructor(t) { Q(this, "_degree"); Q(this, "_knots"); Q(this, "_controlPoints"); Q(this, "_weights"); this._degree = t.degree, this._knots = t.knots, this._controlPoints = t.controlPoints, this._weights = t.weights; } // --- Construction --- static byKnotsControlPointsWeights(t, e, r, s) { return new ut({ degree: t, knots: e, controlPoints: r, weights: s }); } static byPoints(t, e) { const r = Gt(t, e); return new ut(r); } // --- Evaluation --- /** * Evaluate curve point at parameter t (Algorithm A4.1). * Rational curve: C(t) = Σ R_i(t) * P_i where R_i = N_i*w_i / Σ N_j*w_j */ point(t) { const e = this._controlPoints.length - 1, r = st(e, this._degree, t, this._knots), s = gt(r, t, this._degree, this._knots), o = this._controlPoints[0].length, c = new Array(o).fill(0); let l = 0; for (let h = 0; h <= this._degree; h++) { const f = r - this._degree + h, p = this._weights[f], a = s[h] * p; l += a; for (let i = 0; i < o; i++) c[i] += a * this._controlPoints[f][i]; } if (l !== 0) for (let h = 0; h < o; h++) c[h] /= l; return c; } /** * Compute derivatives of the rational curve at parameter t. * Uses Algorithm A4.2 + Eq. 4.20 from The NURBS Book. * * Returns: derivatives[k] = kth derivative vector */ derivatives(t, e) { const r = this._controlPoints.length - 1, s = this._controlPoints[0].length, o = Math.min(e, this._degree), c = st(r, this._degree, t, this._knots), l = Kt(c, t, this._degree, o, this._knots), h = [], f = []; for (let a = 0; a <= o; a++) { const i = new Array(s).fill(0); let u = 0; for (let g = 0; g <= this._degree; g++) { const m = c - this._degree + g, y = this._weights[m]; for (let w = 0; w < s; w++) i[w] += l[a][g] * y * this._controlPoints[m][w]; u += l[a][g] * y; } h.push(i), f.push(u); } const p = []; for (let a = 0; a <= o; a++) { const i = [...h[a]]; for (let u = 1; u <= a; u++) { const g = dn(a, u); for (let m = 0; m < s; m++) i[m] -= g * f[u] * p[a - u][m]; } for (let u = 0; u < s; u++) i[u] /= f[0]; p.push(i); } for (let a = o + 1; a <= e; a++) p.push(new Array(s).fill(0)); return p; } /** * Compute tangent vector (first derivative, NOT normalized) at parameter t. */ tangent(t) { return this.derivatives(t, 1)[1]; } /** * Compute arc length using Gauss-Legendre quadrature. */ length() { return this.lengthBetween(this._knots[this._degree], this._knots[this._knots.length - this._degree - 1]); } lengthBetween(t, e) { const r = [-0.9061798459, -0.5384693101, 0, 0.5384693101, 0.9061798459], s = [0.2369268851, 0.4786286705, 0.5688888889, 0.4786286705, 0.2369268851], o = Math.max(this._controlPoints.length * 2, 8); let c = 0; for (let l = 0; l < o; l++) { const h = t + (e - t) * l / o, f = t + (e - t) * (l + 1) / o, p = (f - h) / 2, a = (h + f) / 2; let i = 0; for (let u = 0; u < r.length; u++) { const g = a + p * r[u], m = this.tangent(g), y = Math.sqrt(m.reduce((w, v) => w + v * v, 0)); i += s[u] * y; } c += p * i; } return c; } /** * Find the parameter of the closest point on the curve to a given point. * Implements Algorithm A6.1 from "The NURBS Book" (Piegl & Tiller), Section 6.1. * * Phase 1: Initial guess via control polygon (Greville abscissa of closest control point) * + refinement by sampling within the support of that basis function. * Phase 2: Newton iteration with four convergence criteria: * (1) Point coincidence: ||C(t) - P|| < eps1 * (2) Zero cosine: |C'(t) · (C(t) - P)| / (|C'(t)| · |C(t) - P|) < eps2 * (3) Parameter correction: |Δt| · |C'(t)| < eps1 * (4) Domain bounds */ closestParam(t) { const e = t.length, r = 1e-8, s = 1e-6, o = this._knots[this._degree], c = this._knots[this._knots.length - this._degree - 1], l = this._controlPoints.length - 1; let h = o, f = 1 / 0; for (let w = 0; w <= l; w++) { let v = 0; for (let x = 0; x < e; x++) v += (this._controlPoints[w][x] - t[x]) ** 2; if (v < f) { f = v; let x = 0; for (let d = 1; d <= this._degree; d++) x += this._knots[w + d]; x /= this._degree, h = Math.max(o, Math.min(c, x)); } } const p = 0.2 * (c - o), a = Math.max(o, h - p), i = Math.min(c, h + p), u = 20; for (let w = 0; w <= u; w++) { const v = a + (i - a) * w / u, x = this.point(v); let d = 0; for (let P = 0; P < e; P++) d += (x[P] - t[P]) ** 2; d < f && (f = d, h = v); } const g = Math.max(l * 4, 20); for (let w = 0; w <= g; w++) { const v = o + (c - o) * w / g, x = this.point(v); let d = 0; for (let P = 0; P < e; P++) d += (x[P] - t[P]) ** 2; d < f && (f = d, h = v); } let m = h; const y = Math.min(2, this._degree); for (let w = 0; w < 50; w++) { const v = this.derivatives(m, y), x = v[0], d = v[1], P = x.map((I, N) => I - t[N]); let M = 0; for (let I = 0; I < e; I++) M += P[I] ** 2; if (Math.sqrt(M) < r) break; let k = 0, b = 0; for (let I = 0; I < e; I++) k += d[I] * P[I], b += d[I] ** 2; b = Math.sqrt(b); const A = Math.sqrt(M); if (b > 0 && A > 0 && Math.abs(k) / (b * A) < s) break; let _ = k, C = b * b; if (y >= 2) { const I = v[2]; for (let N = 0; N < e; N++) C += I[N] * P[N]; } if (Math.abs(C) < 1e-14) break; const S = -_ / C; if (Math.abs(S) * b < r) break; const V = Math.max(o, Math.min(c, m + S)); if (Math.abs(V - m) < 1e-14) break; m = V; } return m; } closestPoint(t) { return this.point(this.closestParam(t)); } /** * Divide curve into segments of equal arc length. * Returns array of { u, pt } where u is the parameter and pt is the 3D point. */ divideByEqualArcLength(t) { const r = this.length() / t, s = this._knots[this._degree], o = this._knots[this._knots.length - this._degree - 1], c = []; c.push({ u: s, pt: this.point(s) }); const l = Math.max(t * 20, 200); let h = 0, f = r, p = this.point(s), a = 1; for (let i = 1; i <= l && a < t; i++) { const u = s + (o - s) * i / l, g = this.point(u); let m = 0; for (let y = 0; y < p.length; y++) m += (g[y] - p[y]) ** 2; for (m = Math.sqrt(m), h += m; h >= f && a < t; ) { const y = h - f, w = m > 0 ? (m - y) / m : 0, v = s + (o - s) * (i - 1) / l, x = v + w * (u - v); c.push({ u: x, pt: this.point(x) }), f += r, a++; } p = g; } return c.push({ u: o, pt: this.point(o) }), c; } /** * Split curve at parameter t. * Inserts knot t until multiplicity = degree, then splits the data at that point. */ split(t) { const e = this._degree; let r = this.asData(), s = 0; for (const y of r.knots) Math.abs(y - t) < 1e-10 && s++; const o = e - s; for (let y = 0; y < o; y++) r = Tt(r, t, 1); let c = -1; for (let y = 0; y < r.knots.length; y++) if (Math.abs(r.knots[y] - t) < 1e-10) { c = y; break; } if (c === -1) return [this.clone()]; const l = c - 1, h = [...r.knots.slice(0, c + 1)]; for (; h.length < l + 1 + e + 1; ) h.push(t); const f = r.controlPoints.slice(0, l + 1), p = r.weights.slice(0, l + 1), a = []; for (let y = 0; y < e + 1; y++) a.push(t); a.push(...r.knots.slice(c + e)); const i = r.controlPoints.slice(l), u = r.weights.slice(l), g = f.length > 0 && h.length === f.length + e + 1, m = i.length > 0 && a.length === i.length + e + 1; return !g || !m ? (console.warn("Curve split produced invalid data, returning approximation"), [this.clone()]) : [ new ut({ degree: e, knots: h, controlPoints: f, weights: p }), new ut({ degree: e, knots: a, controlPoints: i, weights: u }) ]; } reverse() { const t = this._knots[this._knots.length - 1], e = this._knots[0]; return new ut({ degree: this._degree, knots: this._knots.map((r) => t + e - r).reverse(), controlPoints: [...this._controlPoints].reverse(), weights: [...this._weights].reverse() }); } clone() { return new ut({ degree: this._degree, knots: [...this._knots], controlPoints: this._controlPoints.map((t) => [...t]), weights: [...this._weights] }); } // --- Accessors --- degree() { return this._degree; } knots() { return this._knots; } controlPoints() { return this._controlPoints; } weights() { return this._weights; } asData() { return { degree: this._degree, knots: [...this._knots], controlPoints: this._controlPoints.map((t) => [...t]), weights: [...this._weights] }; } }; function dn(n, t) { if (t < 0 || t > n) return 0; if (t === 0 || t === n) return 1; let e = 1; for (let r = 0; r < t; r++) e = e * (n - r) / (r + 1); return Math.round(e); } let J = class Ut { // [u][v] constructor(t) { Q(this, "_degreeU"); Q(this, "_degreeV"); Q(this, "_knotsU"); Q(this, "_knotsV"); Q(this, "_controlPoints"); // [u][v][xyz] Q(this, "_weights"); this._degreeU = t.degreeU, this._degreeV = t.degreeV, this._knotsU = t.knotsU, this._knotsV = t.knotsV, this._controlPoints = t.controlPoints, this._weights = t.weights; } // --- Construction --- static byKnotsControlPointsWeights(t, e, r, s, o, c) { return new Ut({ degreeU: t, degreeV: e, knotsU: r, knotsV: s, controlPoints: o, weights: c }); } static byLoftingCurves(t, e) { return new Ut(gn(t.map((r) => r.asData()), e)); } static byCorners(t, e, r, s) { return new Ut({ degreeU: 1, degreeV: 1, knotsU: [0, 0, 1, 1], knotsV: [0, 0, 1, 1], controlPoints: [[t, s], [e, r]], weights: [[1, 1], [1, 1]] }); } // --- Evaluation --- /** * Evaluate surface point at (u, v) — Algorithm A4.3 (rational tensor product). */ point(t, e) { const r = this._controlPoints.length - 1, s = this._controlPoints[0].length - 1, o = this._controlPoints[0][0].length, c = st(r, this._degreeU, t, this._knotsU), l = st(s, this._degreeV, e, this._knotsV), h = gt(c, t, this._degreeU, this._knotsU), f = gt(l, e, this._degreeV, this._knotsV), p = new Array(o).fill(0); let a = 0; for (let i = 0; i <= this._degreeU; i++) { const u = c - this._degreeU + i; for (let g = 0; g <= this._degreeV; g++) { const m = l - this._degreeV + g, y = this._weights[u][m], w = h[i] * f[g] * y; a += w; for (let v = 0; v < o; v++) p[v] += w * this._controlPoints[u][m][v]; } } if (a !== 0) for (let i = 0; i < o; i++) p[i] /= a; return p; } /** * Compute surface normal at (u, v) as cross product of partial derivatives. */ normal(t, e) { const r = this.derivatives(t, e, 1), s = r[1][0], o = r[0][1]; return [ s[1] * o[2] - s[2] * o[1], s[2] * o[0] - s[0] * o[2], s[0] * o[1] - s[1] * o[0] ]; } /** * Compute partial derivatives of the rational surface at (u, v). * Returns ders[k][l] = mixed partial derivative ∂^(k+l)S / ∂u^k ∂v^l. * Algorithm A4.4 adapted for rational surfaces (Eq. 4.20 generalized). */ derivatives(t, e, r) { const s = this._controlPoints.length - 1, o = this._controlPoints[0].length - 1, c = this._controlPoints[0][0].length, l = Math.min(r, this._degreeU), h = Math.min(r, this._degreeV), f = st(s, this._degreeU, t, this._knotsU), p = st(o, this._degreeV, e, this._knotsV), a = Kt(f, t, this._degreeU, l, this._knotsU), i = Kt(p, e, this._degreeV, h, this._knotsV), u = [], g = []; for (let y = 0; y <= l; y++) { u[y] = [], g[y] = []; for (let w = 0; w <= h; w++) { const v = new Array(c).fill(0); let x = 0; for (let d = 0; d <= this._degreeU; d++) { const P = f - this._degreeU + d; for (let M = 0; M <= this._degreeV; M++) { const k = p - this._degreeV + M, b = this._weights[P][k], A = a[y][d] * i[w][M] * b; for (let _ = 0; _ < c; _++) v[_] += A * this._controlPoints[P][k][_]; x += A; } } u[y][w] = v, g[y][w] = x; } } const m = []; for (let y = 0; y <= r; y++) { m[y] = []; for (let w = 0; w <= r; w++) { if (y > l || w > h) { m[y][w] = new Array(c).fill(0); continue; } const v = [...u[y][w]]; for (let x = 1; x <= w; x++) { const d = Wt(w, x); for (let P = 0; P < c; P++) v[P] -= d * g[0][x] * m[y][w - x][P]; } for (let x = 1; x <= y; x++) { const d = Wt(y, x); for (let P = 0; P < c; P++) v[P] -= d * g[x][0] * m[y - x][w][P]; } for (let x = 0; x < c; x++) v[x] /= g[0][0]; m[y][w] = v; } } return m; } /** * Find closest UV parameters to a 3D point. * Implements the surface point projection from "The NURBS Book" Section 6.1. * * Phase 1: Initial guess via closest control point (Greville abscissa) * + grid refinement near the best candidate. * Phase 2: Newton iteration with four convergence criteria: * (1) Point coincidence: ||S(u,v) - P|| < eps1 * (2) Zero cosine (U): |S_u · (S - P)| / (|S_u| · |S - P|) < eps2 * (2) Zero cosine (V): |S_v · (S - P)| / (|S_v| · |S - P|) < eps2 * (3) Parameter correction: |Δu·S_u + Δv·S_v| < eps1 * (4) Domain bounds */ closestParam(t) { const e = t.length, r = this._controlPoints.length - 1, s = this._controlPoints[0].length - 1, o = this._knotsU[this._degreeU], c = this._knotsU[r + 1], l = this._knotsV[this._degreeV], h = this._knotsV[s + 1], f = 1e-8, p = 1e-6; let a = (o + c) / 2, i = (l + h) / 2, u = 1 / 0; for (let w = 0; w <= r; w++) { let v = 0; for (let x = 1; x <= this._degreeU; x++) v += this._knotsU[w + x]; v /= this._degreeU; for (let x = 0; x <= s; x++) { let d = 0; for (let P = 0; P < e; P++) d += (this._controlPoints[w][x][P] - t[P]) ** 2; if (d < u) { u = d, a = Math.max(o, Math.min(c, v)); let P = 0; for (let M = 1; M <= this._degreeV; M++) P += this._knotsV[x + M]; P /= this._degreeV, i = Math.max(l, Math.min(h, P)); } } } const g = 20; for (let w = 0; w <= g; w++) for (let v = 0; v <= g; v++) { const x = o + (c - o) * w / g, d = l + (h - l) * v / g, P = this.point(x, d); let M = 0; for (let k = 0; k < e; k++) M += (P[k] - t[k]) ** 2; M < u && (u = M, a = x, i = d); } let m = a, y = i; for (let w = 0; w < 50; w++) { const v = this.derivatives(m, y, 1), x = v[0][0], d = v[1][0], P = v[0][1], M = x.map((K, Y) => K - t[Y]); let k = 0; for (let K = 0; K < e; K++) k += M[K] ** 2; const b = Math.sqrt(k); if (b < f) break; let A = 0, _ = 0, C = 0, S = 0; for (let K = 0; K < e; K++) A += d[K] * M[K], _ += P[K] * M[K], C += d[K] ** 2, S += P[K] ** 2; C = Math.sqrt(C), S = Math.sqrt(S); const V = C > 0 && b > 0 ? Math.abs(A) / (C * b) : 0, I = S > 0 && b > 0 ? Math.abs(_) / (S * b) : 0; if (V < p && I < p) break; let N = 0, E = 0, q = 0; for (let K = 0; K < e; K++) N += d[K] * d[K], E += d[K] * P[K], q += P[K] * P[K]; const Z = N * q - E * E; if (Math.abs(Z) < 1e-14) break; const H = -(q * A - E * _) / Z, B = -(N * _ - E * A) / Z; let O = 0; for (let K = 0; K < e; K++) { const Y = H * d[K] + B * P[K]; O += Y * Y; } if (Math.sqrt(O) < f) break; const ot = Math.max(o, Math.min(c, m + H)), bt = Math.max(l, Math.min(h, y + B)); if (Math.abs(ot - m) + Math.abs(bt - y) < 1e-14) break; m = ot, y = bt; } return [m, y]; } /** * Extract an iso-parametric curve from the surface. * If useV=false: fix u=param, extract curve in V direction. * If useV=true: fix v=param, extract curve in U direction. */ /** * Extract an iso-parametric curve from the surface. * If useV=true: fix v=param, extract curve in U direction. * If useV=false: fix u=param, extract curve in V direction. * * Works in homogeneous coordinates: for each row/column, blend the * homogeneous control points (w*P, w) using basis functions, then * store the result as the new curve's control points and weights. */ isocurve(t, e) { if (e) { const r = this._controlPoints.length - 1, s = this._controlPoints[0].length - 1, o = this._controlPoints[0][0].length, c = st(s, this._degreeV, t, this._knotsV), l = gt(c, t, this._degreeV, this._knotsV), h = [], f = []; for (let p = 0; p <= r; p++) { const a = new Array(o).fill(0); let i = 0; for (let u = 0; u <= this._degreeV; u++) { const g = c - this._degreeV + u, m = this._weights[p][g]; for (let y = 0; y < o; y++) a[y] += l[u] * m * this._controlPoints[p][g][y]; i += l[u] * m; } h.push(i !== 0 ? a.map((u) => u / i) : a), f.push(i); } return L.byKnotsControlPointsWeights(this._degreeU, [...this._knotsU], h, f); } else { const r = this._controlPoints.length - 1, s = this._controlPoints[0].length - 1, o = this._controlPoints[0][0].length, c = st(r, this._degreeU, t, this._knotsU), l = gt(c, t, this._degreeU, this._knotsU), h = [], f = []; for (let p = 0; p <= s; p++) { const a = new Array(o).fill(0); let i = 0; for (let u = 0; u <= this._degreeU; u++) { const g = c - this._degreeU + u, m = this._weights[g][p]; for (let y = 0; y < o; y++) a[y] += l[u] * m * this._controlPoints[g][p][y]; i += l[u] * m; } h.push(i !== 0 ? a.map((u) => u / i) : a), f.push(i); } return L.byKnotsControlPointsWeights(this._degreeV, [...this._knotsV], h, f); } } // --- Accessors --- degreeU() { return this._degreeU; } degreeV() { return this._degreeV; } knotsU() { return this._knotsU; } knotsV() { return this._knotsV; } controlPoints() { return this._controlPoints; } weights() { return this._weights; } asData() { return { degreeU: this._degreeU, degreeV: this._degreeV, knotsU: [...this._knotsU], knotsV: [...this._knotsV], controlPoints: this._controlPoints.map((t) => t.map((e) => [...e])), weights: this._weights.map((t) => [...t]) }; } }; function Wt(n, t) { if (t < 0 || t > n) return 0; if (t === 0 || t === n) return 1; let e = 1; for (let r = 0; r < t; r++) e = e * (n - r) / (r + 1); return Math.round(e); } function W(n, t) { return n.map((e, r) => e + t[r]); } function xt(n, t) { return n.map((e, r) => e - t[r]); } function $(n, t) { return n.map((e) => e * t); } function Rt(n, t) { return n.reduce((e, r, s) => e + r * t[s], 0); } function It(n, t) { return [ n[1] * t[2] - n[2] * t[1], n[2] * t[0] - n[0] * t[2], n[0] * t[1] - n[1] * t[0] ]; } function ct(n) { const t = Math.sqrt(Rt(n, n)); return t > 0 ? $(n, 1 / t) : n; } function Ft(n) { return Math.sqrt(Rt(n, n)); } function at(n, t, e, r, s, o) { const c = ct(t), l = ct(e); let h = o - s; h < 0 && (h += 2 * Math.PI); let f; h <= Math.PI / 2 ? f = 1 : h <= Math.PI ? f = 2 : h <= 3 * Math.PI / 2 ? f = 3 : f = 4; const p = h / f, a = Math.cos(p / 2); let i = W(n, W($(c, r * Math.cos(s)), $(l, r * Math.sin(s)))), u = W($(c, -Math.sin(s)), $(l, Math.cos(s))); const g = [i], m = [1]; let y = s; for (let v = 0; v < f; v++) { y += p; const x = W(n, W($(c, r * Math.cos(y)), $(l, r * Math.sin(y)))), d = W($(c, -Math.sin(y)), $(l, Math.cos(y))), P = xt(x, i), M = u[0] * d[1] - u[1] * d[0]; let k; if (Math.abs(M) > 1e-14) k = (P[0] * d[1] - P[1] * d[0]) / M; else { const A = u[0] * d[2] - u[2] * d[0]; Math.abs(A) > 1e-14 ? k = (P[0] * d[2] - P[2] * d[0]) / A : k = (P[1] * d[2] - P[2] * d[1]) / (u[1] * d[2] - u[2] * d[1]); } const b = W(i, $(u, k)); g.push(b, x), m.push(a, 1), i = x, u = d; } const w = []; w.push(0, 0, 0); for (let v = 1; v < f; v++) { const x = v / f; w.push(x, x); } return w.push(1, 1, 1), { degree: 2, knots: w, controlPoints: g, weights: m }; } function mn(n, t, e, r) { return at(n, t, e, r, 0, 2 * Math.PI); } function Jt(n, t, e, r, s) { const o = Ft(t), c = Ft(e), l = ct(t), h = ct(e); let f = s - r; f < 0 && (f += 2 * Math.PI); let p; f <= Math.PI / 2 ? p = 1 : f <= Math.PI ? p = 2 : f <= 3 * Math.PI / 2 ? p = 3 : p = 4; const a = f / p, i = Math.cos(a / 2); let u = W(n, W($(l, o * Math.cos(r)), $(h, c * Math.sin(r)))), g = W($(l, -o * Math.sin(r)), $(h, c * Math.cos(r))); const m = [u], y = [1]; let w = r; for (let x = 0; x < p; x++) { w += a; const d = W(n, W($(l, o * Math.cos(w)), $(h, c * Math.sin(w)))), P = W($(l, -o * Math.sin(w)), $(h, c * Math.cos(w))), M = xt(d, u), k = g[0] * P[1] - g[1] * P[0]; let b; if (Math.abs(k) > 1e-14) b = (M[0] * P[1] - M[1] * P[0]) / k; else { const _ = g[0] * P[2] - g[2] * P[0]; Math.abs(_) > 1e-14 ? b = (M[0] * P[2] - M[2] * P[0]) / _ : b = (M[1] * P[2] - M[2] * P[1]) / (g[1] * P[2] - g[2] * P[1]); } const A = W(u, $(g, b)); m.push(A, d), y.push(i, 1), u = d, g = P; } const v = []; v.push(0, 0, 0); for (let x = 1; x < p; x++) { const d = x / p; v.push(d, d); } return v.push(1, 1, 1), { degree: 2, knots: v, controlPoints: m, weights: y }; } function wn(n, t, e) { return Jt(n, t, e, 0, 2 * Math.PI); } function yn(n, t, e, r, s) { const o = ct(n), c = ct(t), l = It(o, c), h = at(e, c, l, s, 0, 2 * Math.PI), f = $(o, r); return Et(h, f); } function Et(n, t) { const e = n.controlPoints, r = e.map((s) => W(s, t)); return { degreeU: n.degree, degreeV: 1, knotsU: [...n.knots], knotsV: [0, 0, 1, 1], controlPoints: e.map((s, o) => [s, r[o]]), weights: n.weights.map((s) => [s, s]) }; } function vt(n, t, e, r) { const s = ct(e), o = at( [0, 0, 0], [1, 0, 0], [0, 1, 0], 1, 0, r ), c = o.controlPoints.length, l = [], h = []; for (let a = 0; a < n.controlPoints.length; a++) { const i = n.controlPoints[a], u = n.weights[a], g = xt(i, t), m = Rt(g, s), y = W(t, $(s, m)), w = xt(i, y), v = Ft(w); if (v < 1e-14) { l[a] = [], h[a] = []; for (let M = 0; M < c; M++) l[a][M] = [...i], h[a][M] = u * o.weights[M]; continue; } const x = $(w, 1 / v), d = It(s, x), P = at(y, x, d, v, 0, r); l[a] = [], h[a] = []; for (let M = 0; M < P.controlPoints.length; M++) l[a][M] = P.controlPoints[M], h[a][M] = u * P.weights[M]; } const f = [], p = []; for (let a = 0; a < c; a++) { f[a] = [], p[a] = []; for (let i = 0; i < n.controlPoints.length; i++) f[a][i] = l[i][a], p[a][i] = h[i][a]; } return { degreeU: 2, // arc degree degreeV: n.degree, knotsU: o.knots, knotsV: [...n.knots], controlPoints: f, weights: p }; } function xn(n, t) { const e = n.controlPoints.length, r = t.controlPoints.length, s = t.controlPoints[0], o = [], c = []; for (let l = 0; l < r; l++) { o[l] = [], c[l] = []; const h = xt(t.controlPoints[l], s); for (let f = 0; f < e; f++) o[l][f] = W(n.controlPoints[f], h), c[l][f] = t.weights[l] * n.weights[f]; } return { degreeU: t.degree, degreeV: n.degree, knotsU: [...t.knots], knotsV: [...n.knots], controlPoints: o, weights: c }; } function ht(n, t, e, r, s = "forward") { return { surface: { degreeU: 1, degreeV: 1, knotsU: [0, 0, 1, 1], knotsV: [0, 0, 1, 1], controlPoints: [[n, r], [t, e]], weights: [[1, 1], [1, 1]] }, orientation: s }; } function vn(n, t, e, r = [0, 0, 0]) { const [s, o, c] = r, l = [s, o, c], h = [s + n, o, c], f = [s, o + t, c], p = [s + n, o + t, c], a = [s, o, c + e], i = [s + n, o, c + e], u = [s, o + t, c + e], g = [s + n, o + t, c + e]; return { faces: [ // Bottom (z=0), normal pointing -Z ht(l, h, p, f, "reversed"), // Top (z=dz), normal pointing +Z ht(a, i, g, u, "forward"), // Front (y=0), normal pointing -Y ht(l, h, i, a, "reversed"), // Back (y=dy), normal pointing +Y ht(f, p, g, u, "forward"), // Left (x=0), normal pointing -X ht(l, f, u, a, "forward"), // Right (x=dx), normal pointing +X ht(h, p, g, i, "reversed") ] }; } function Pn(n, t, e = [0, 1, 0], r = [0, 0, 0]) { const s = ct(e), o = Math.abs(s[1]) < 0.9 ? [0, 1, 0] : [1, 0, 0], c = ct(It(s, o)), l = It(s, c), h = at(r, c, l, n, 0, 2 * Math.PI), f = $(s, t), p = Et(h, f), a = W(r, f), i = { degree: 1, knots: [0, 0, 1, 1], controlPoints: [ [...r], W(r, $(c, n)) ], weights: [1, 1] }, u = vt( i, r, s, 2 * Math.PI ), g = { degree: 1, knots: [0, 0, 1, 1], controlPoints: [ [...a], W(a, $(c, n)) ], weights: [1, 1] }, m = vt( g, a, s, 2 * Math.PI ); return { faces: [ { surface: p, orientation: "forward" }, { surface: u, orientation: "reversed" }, { surface: m, orientation: "forward" } ] }; } function Mn(n, t = [0, 0, 0]) { const e = at( t, [1, 0, 0], // xaxis [0, 1, 0], // yaxis n, -Math.PI / 2, // start at south pole (0, -R, 0) Math.PI / 2 // end at north pole (0, R, 0) ); return { faces: [ { surface: vt( e, t, [0, 1, 0], // Y axis 2 * Math.PI ), orientation: "forward" } ] }; } class G { constructor(t) { Q(this, "_faces"); this._faces = t.faces; } // --- Construction --- static fromFaces(t) { return new G({ faces: t }); } static makeBox(t, e, r, s = [0, 0, 0]) { return new G(vn(t, e, r, s)); } static makeCylinder(t, e, r = [0, 1, 0], s = [0, 0, 0]) { return new G(Pn(t, e, r, s)); } static makeSphere(t, e = [0, 0, 0]) { return new G(Mn(t, e)); } // --- Curve/Surface → Solid bridges --- /** * Create a solid by revolving a closed curve profile around an axis. * The profile should be a closed loop (or the revolution will close it). * Result: 1 revolved surface face. */ static fromRevolution(t, e = [0, 0, 0], r = [0, 1, 0], s = 2 * Math.PI, o = !1) { const l = [{ surface: vt(t, e, r, s), orientation: "forward" }]; if (o && s < 2 * Math.PI - 0.01) { const h = Math.sqrt(r[0] ** 2 + r[1] ** 2 + r[2] ** 2), f = r[0] / h, p = r[1] / h, a = r[2] / h, i = Ct(t, e); l.push({ surface: i, orientation: "reversed" }); const u = Math.cos(s), g = Math.sin(s), m = { ...t, controlPoints: t.controlPoints.map((w) => { const v = w[0] - e[0], x = w[1] - e[1], d = w[2] - e[2], P = v * f + x * p + d * a, M = v * u + (p * d - a * x) * g + f * P * (1 - u), k = x * u + (a * v - f * d) * g + p * P * (1 - u), b = d * u + (f * x - p * v) * g + a * P * (1 - u); return [e[0] + M, e[1] + k, e[2] + b]; }) }, y = Ct(m, e); l.push({ surface: y, orientation: "forward" }); } return new G({ faces: l }); } /** * Create a solid by extruding a closed curve profile along a direction. * Result: 1 extruded surface (sides) + 2 caps. * Caps use a degenerate surface where one edge matches the profile exactly * and the opposite edge collapses to the centroid. */ static fromExtrusion(t, e, r = !1) { const o = [{ surface: Et(t, e), orientation: "forward" }]; if (r) { const c = t.controlPoints.length, l = t.weights.reduce((u, g) => u + g, 0), h = [0, 0, 0]; for (let u = 0; u < c; u++) { const g = t.weights[u] / l; h[0] += t.controlPoints[u][0] * g, h[1] += t.controlPoints[u][1] * g, h[2] += t.controlPoints[u][2] * g; } const f = Ct(t, h); o.push({ surface: f, orientation: "reversed" }); const p = [ h[0] + e[0], h[1] + e[1], h[2] + e[2] ], a = { ...t, controlPoints: t.controlPoints.map((u) => [ u[0] + e[0], u[1] + e[1], u[2] + e[2] ]) }, i = Ct(a, p); o.push({ surface: i, orientation: "forward" }); } return new G({ faces: o }); } /** * Create a solid by thickening a surface. * Offsets the surface by `thickness` along its normal direction, * creating two faces (original + offset) connected by side surfaces. * Simplified version: returns original + offset face only. */ static fromSurface(t, e) { const r = new J(t), s = t.controlPoints.map( (w, v) => w.map((x, d) => { const P = v / (t.controlPoints.length - 1), M = d / (w.length - 1); try { const k = r.normal(P, M), b = Math.sqrt(k[0] ** 2 + k[1] ** 2 + k[2] ** 2); if (b > 0) return [ x[0] + k[0] / b * e, x[1] + k[1] / b * e, x[2] + k[2] / b * e ]; } catch { } return [x[0], x[1] + e, x[2]]; }) ), o = { ...t, controlPoints: s }, c = t.controlPoints.length, l = t.controlPoints[0].length, h = [], f = t.controlPoints.map((w) => w[0]), p = s.map((w) => w[0]); h.push({ surface: _t(f, p, t.degreeU, t.knotsU), orientation: "forward" }); const a = t.controlPoints.map((w) => w[l - 1]), i = s.map((w) => w[l - 1]); h.push({ surface: _t(a, i, t.degreeU, t.knotsU), orientation: "reversed" }); const u = t.controlPoints[0], g = s[0]; h.push({ surface: _t(u, g, t.degreeV, t.knotsV), orientation: "reversed" }); const m = t.controlPoints[c - 1], y = s[c - 1]; return h.push({ surface: _t(m, y, t.degreeV, t.knotsV), orientation: "forward" }), new G({ faces: [ { surface: t, orientation: "forward" }, { surface: o, orientation: "reversed" }, ...h ] }); } /** * Create a solid face from a surface (wraps it as a FaceData). * Useful for composing solids from individual surfaces. */ static faceFromSurface(t, e = "forward", r) { return { surface: t, outerWire: r, orientation: e }; } // --- Accessors --- faces() { return this._faces; } numFaces() { return this._faces.length; } asData() { return { faces: this._faces.map((t) => { var e, r; return { surface: { degreeU: t.surface.degreeU, degreeV: t.surface.degreeV, knotsU: [...t.surface.knotsU], knotsV: [...t.surface.knotsV], controlPoints: t.surface.controlPoints.map( (s) => s.map((o) => [...o]) ), weights: t.surface.weights.map((s) => [...s]) }, outerWire: (e = t.outerWire) == null ? void 0 : e.map((s) => ({ degree: s.degree, knots: [...s.knots], controlPoints: s.controlPoints.map((o) => [...o]), weights: [...s.weights] })), holes: (r = t.holes) == null ? void 0 : r.map( (s) => s.map((o) => ({ degree: o.degree, knots: [...o.knots], controlPoints: o.controlPoints.map((c) => [...c]), weights: [...o.weights] })) ), orientation: t.orientation }; }) }; } clone() { return new G(this.asData()); } } function Ct(n, t) { return { degreeU: n.degree, degreeV: 1, knotsU: [...n.knots], knotsV: [0, 0, 1, 1], // Each row has 2 points: [center, profileControlPoint] // At V=0 all points collapse to center, at V=1 they trace the profile controlPoints: n.controlPoints.map((e) => [[...t], [...e]]), // Weights match the profile in U, uniform in V weights: n.weights.map((e) => [1, e]) }; } function _t(n, t, e, r) { return { degreeU: e, degreeV: 1, knotsU: [...r], knotsV: [0, 0, 1, 1], controlPoints: n.map((s, o) => [s, t[o]]), weights: n.map(() => [1, 1]) }; } function kn(n, t = {}) { const { tolerance: e = 0.01, normalTolerance: r, minDivsU: s = 4, minDivsV: o = 4, maxDepth: c = 6, normals: l = !0 } = t, h = r !== void 0 && r > 0, f = h ? Math.cos(r) : 1, p = [], a = [], i = /* @__PURE__ */ new Map(); function u(d, P) { const M = `${d.toFixed(10)},${P.toFixed(10)}`, k = i.get(M); if (k !== void 0) return k; const b = n.point(d, P); let A = [0, 0, 1]; if (l) try { const C = n.normal(d, P), S = Math.sqrt(C[0] ** 2 + C[1] ** 2 + C[2] ** 2); S > 0 && (A = [C[0] / S, C[1] / S, C[2] / S]); } catch { } const _ = p.length; return p.push({ pos: b, normal: A, uv: [d, P] }), i.set(M, _), _; } function g(d, P, M, k) { const b = (d + M) / 2, A = (P + k) / 2, _ = n.point(b, A), C = n.point(d, P), S = n.point(M, P), V = n.point(d, k), I = n.point(M, k), N = [ 0.25 * (C[0] + S[0] + V[0] + I[0]), 0.25 * (C[1] + S[1] + V[1] + I[1]), 0.25 * (C[2] + S[2] + V[2] + I[2]) ], E = _[0] - N[0], q = _[1] - N[1], Z = _[2] - N[2]; if (Math.sqrt(E * E + q * q + Z * Z) > e) return !0; if (h) try { const B = n.normal(b, A), O = Math.sqrt(B[0] ** 2 + B[1] ** 2 + B[2] ** 2); if (O > 0) { const ot = [[d, P], [M, P], [d, k], [M, k]]; for (const [bt, K] of ot) { const Y = n.normal(bt, K), qt = Math.sqrt(Y[0] ** 2 + Y[1] ** 2 + Y[2] ** 2); if (qt > 0 && (B[0] * Y[0] + B[1] * Y[1] + B[2] * Y[2]) / (O * qt) < f) return !0; } } } catch { } return !1; } function m(d, P, M, k, b) { if (b < c && g(d, P, M, k)) { const A = (d + M) / 2, _ = (P + k) / 2; m(d, P, A, _, b + 1), m(A, P, M, _, b + 1), m(d, _, A, k, b + 1), m(A, _, M, k, b + 1); } else { const A = u(d, P), _ = u(M, P), C = u(d, k), S = u(M, k); a.push(A, _, C), a.push(_, S, C); } } for (let d = 0; d < s; d++) for (let P = 0; P < o; P++) { const M = d / s, k = (d + 1) / s, b = P / o, A = (P + 1) / o; m(M, b, k, A, 0); } const y = p.length, w = new Float32Array(y * 3), v = new Float32Array(y * 3), x = new Float32Array(y * 2); for (let d = 0; d < y; d++) w[d * 3] = p[d].pos[0], w[d * 3 + 1] = p[d].pos[1], w[d * 3 + 2] = p[d].pos[2], v[d * 3] = p[d].normal[0], v[d * 3 + 1] = p[d].normal[1], v[d * 3 + 2] = p[d].normal[2], x[d * 2] = p[d].uv[0], x[d * 2 + 1] = p[d].uv[1]; return { vertices: w, normals: v, uvs: x, indices: new Uint32Array(a) }; } function bn(n, t, e = 1e-3, r = 40) { const s = r, o = [], c = []; for (let f = 0; f <= s; f++) { o[f] = [], c[f] = []; for (let p = 0; p <= s; p++) { const a = f / s, i = p / s, u = n.point(a, i), g = t.closestParam(u), m = t.point(g[0], g[1]), y = n.normal(a, i), w = Math.sqrt(y[0] ** 2 + y[1] ** 2 + y[2] ** 2), v = [u[0] - m[0], u[1] - m[1], u[2] - m[2]]; let d = Math.sqrt(v[0] ** 2 + v[1] ** 2 + v[2] ** 2); w > 1e-14 && (d = (v[0] * y[0] + v[1] * y[1] + v[2] * y[2]) / w), o[f][p] = d, c[f][p] = u; } } const l = []; for (let f = 0; f < s; f++) for (let p = 0; p < s; p++) { const a = [ { d: o[f][p], pt: c[f][p] }, { d: o[f + 1][p], pt: c[f + 1][p] }, { d: o[f + 1][p + 1], pt: c[f + 1][p + 1] }, { d: o[f][p + 1], pt: c[f][p + 1] } ]; for (let i = 0; i < 4; i++) { const u = a[i], g = a[(i + 1) % 4]; if (u.d >= 0 != g.d >= 0) { const m = u.d / (u.d - g.d); l.push([ u.pt[0] + m * (g.pt[0] - u.pt[0]), u.pt[1] + m * (g.pt[1] - u.pt[1]), u.pt[2] + m * (g.pt[2] - u.pt[2]) ]); } } } return l.length < 2 ? [] : Cn(l, e * 10).map((f) => ({ points: f })); } function Cn(n, t) { if (n.length === 0) return []; const e = /* @__PURE__ */ new Set(), r = []; for (; e.size < n.length; ) { let s = -1; for (let l = 0; l < n.length; l++) if (!e.has(l)) { s = l; break; } if (s === -1) break; const o = [n[s]]; e.add(s); let c = !0; for (; c; ) { c = !1; const l = o[o.length - 1]; let h = -1, f = t * t; for (let p = 0; p < n.length; p++) { if (e.has(p)) continue; const a = n[p][0] - l[0], i = n[p][1] - l[1], u = n[p][2] - l[2], g = a * a + i * i + u * u; g < f && (f = g, h = p); } h >= 0 && (o.push(n[h]), e.add(h), c = !0); } o.length >= 2 && r.push(o); } return r; } function T(n, t) { const e = n + t + 1; return Array(e).fill(0).map((r, s) => s < t + 1 ? 0 : s >= e - t - 1 ? 1 : (s - t) / (e - 2 * t - 1)); } function _n(n, t, e) { const r = n.point(t, e); return new U(r[0], r[1], r[2]); } function An(n, t, e) { try { const r = n.normal(t, e), s = new U(r[0], r[1], r[2]), o = s.length(); return o > 0 ? s.divideScalar(o) : new U(0, 1, 0); } catch { const s = n.point(t, e), o = n.point(Math.min(t + 1e-4, 1), e), c = n.point(t, Math.min(e + 1e-4, 1)), l = new U().subVectors(new U(...o), new U(...s)), h = new U().subVectors(new U(...c), new U(...s)), f = new U().crossVectors(l, h).normalize(); return f.length() > 0 ? f : new U(0, 1, 0); } } function le(n, t = 100) { return Array.from({ length: t }, (e, r) => { const s = r / (t - 1), o = n.point(s); return [o[0], o[1]]; }); } function Sn(n, t = 5, e = 10) { const r = (f) => [f[0], f[1]], s = (f, p, a) => { const i = [p[0] - f[0], p[1] - f[1]], u = [a[0] - p[0], a[1] - p[1]], g = i[0] * u[0] + i[1] * u[1], m = Math.sqrt(i[0] * i[0] + i[1] * i[1]), y = Math.sqrt(u[0] * u[0] + u[1] * u[1]); return m === 0 || y === 0 ? 0 : Math.acos(Math.max(-1, Math.min(1, g / (m * y)))) * (180 / Math.PI); }; function o(f, p, a) { const i = r(n.point(f)), u = r(n.point((f + p) / 2)), g = r(n.point(p)); if (s(i, u, g) > t && a < e) { const y = o(f, (f + p) / 2, a + 1), w = o((f + p) / 2, p, a + 1); return [...y.slice(0, -1), ...w]; } else return [i, g]; } const c = [], l = 10; let h = null; for (let f = 0; f < l; f++) { const p = o(f / l, (f + 1) / l, 0); for (const a of p) (!h || a[0] !== h[0] || a[1] !== h[1]) && (c.push(a), h = a); } return c; } function ue(n, t) { try { const e = n.closestParam(t); return [e[0], e[1]]; } catch { return null; } } function jn(n, t, e = 100) { const r = [], s = t.point(0), o = n.closestParam(s); r.push([o[0], o[1]]); for (let c = 1; c <= e; c++) { const l = c / e, h = t.point(l), f = r[r.length - 1], p = Un(n, h, f[0], f[1]); r.push(p); } return r; } function Un(n, t, e, r) { const s = t.length, o = 1e-8, c = 1e-6; let l = e, h = r; for (let f = 0; f < 20; f++) { const p = n.derivatives(l, h, 1), a = p[0][0], i = p[1][0], u = p[0][1], g = a.map((C, S) => C - t[S]); let m = 0; for (let C = 0; C < s; C++) m += g[C] ** 2; if (Math.sqrt(m) < o) break; let y = 0, w = 0, v = 0, x = 0; for (let C = 0; C < s; C++) y += i[C] * g[C], w += u[C] * g[C], v += i[C] ** 2, x += u[C] ** 2; v = Math.sqrt(v), x = Math.sqrt(x); const d = Math.sqrt(m); if (v > 0 && x > 0 && d > 0 && Math.abs(y) / (v * d) < c && Math.abs(w) / (x * d) < c) break; let P = 0, M = 0, k = 0; for (let C = 0; C < s; C++) P += i[C] * i[C], M += i[C] * u[C], k += u[C] * u[C]; const b = P * k - M * M; if (Math.abs(b) < 1e-14) break; const A = -(k * y - M * w) / b, _ = -(P * w - M * y) / b; l = Math.max(0, Math.min(1, l + A)), h