@ccpc/math 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/dist/constants/geom_type.d.ts +13 -0
  4. package/dist/constants/geom_type.d.ts.map +1 -0
  5. package/dist/constants/geom_type.js +17 -0
  6. package/dist/constants/math_const.d.ts +9 -0
  7. package/dist/constants/math_const.d.ts.map +1 -0
  8. package/dist/constants/math_const.js +12 -0
  9. package/dist/core/box2.d.ts +71 -0
  10. package/dist/core/box2.d.ts.map +1 -0
  11. package/dist/core/box2.js +243 -0
  12. package/dist/core/coord2d.d.ts +62 -0
  13. package/dist/core/coord2d.d.ts.map +1 -0
  14. package/dist/core/coord2d.js +155 -0
  15. package/dist/core/geom_base.d.ts +19 -0
  16. package/dist/core/geom_base.d.ts.map +1 -0
  17. package/dist/core/geom_base.js +18 -0
  18. package/dist/core/mat3.d.ts +101 -0
  19. package/dist/core/mat3.d.ts.map +1 -0
  20. package/dist/core/mat3.js +290 -0
  21. package/dist/core/vec2.d.ts +138 -0
  22. package/dist/core/vec2.d.ts.map +1 -0
  23. package/dist/core/vec2.js +297 -0
  24. package/dist/curves/arc2.d.ts +49 -0
  25. package/dist/curves/arc2.d.ts.map +1 -0
  26. package/dist/curves/arc2.js +265 -0
  27. package/dist/curves/bspline2.d.ts +150 -0
  28. package/dist/curves/bspline2.d.ts.map +1 -0
  29. package/dist/curves/bspline2.js +793 -0
  30. package/dist/curves/circle2.d.ts +42 -0
  31. package/dist/curves/circle2.d.ts.map +1 -0
  32. package/dist/curves/circle2.js +135 -0
  33. package/dist/curves/circle_curve2.d.ts +38 -0
  34. package/dist/curves/circle_curve2.d.ts.map +1 -0
  35. package/dist/curves/circle_curve2.js +112 -0
  36. package/dist/curves/curve2.d.ts +214 -0
  37. package/dist/curves/curve2.d.ts.map +1 -0
  38. package/dist/curves/curve2.js +238 -0
  39. package/dist/curves/ellipse2.d.ts +42 -0
  40. package/dist/curves/ellipse2.d.ts.map +1 -0
  41. package/dist/curves/ellipse2.js +125 -0
  42. package/dist/curves/ellipse_arc2.d.ts +49 -0
  43. package/dist/curves/ellipse_arc2.d.ts.map +1 -0
  44. package/dist/curves/ellipse_arc2.js +184 -0
  45. package/dist/curves/ellipse_curve2.d.ts +56 -0
  46. package/dist/curves/ellipse_curve2.d.ts.map +1 -0
  47. package/dist/curves/ellipse_curve2.js +262 -0
  48. package/dist/curves/interval.d.ts +112 -0
  49. package/dist/curves/interval.d.ts.map +1 -0
  50. package/dist/curves/interval.js +200 -0
  51. package/dist/curves/line2.d.ts +64 -0
  52. package/dist/curves/line2.d.ts.map +1 -0
  53. package/dist/curves/line2.js +193 -0
  54. package/dist/curves/period_interval.d.ts +129 -0
  55. package/dist/curves/period_interval.d.ts.map +1 -0
  56. package/dist/curves/period_interval.js +240 -0
  57. package/dist/discretize/discretize_defaults.d.ts +12 -0
  58. package/dist/discretize/discretize_defaults.d.ts.map +1 -0
  59. package/dist/discretize/discretize_defaults.js +12 -0
  60. package/dist/discretize/discretize_engine.d.ts +33 -0
  61. package/dist/discretize/discretize_engine.d.ts.map +1 -0
  62. package/dist/discretize/discretize_engine.js +347 -0
  63. package/dist/discretize/discretize_errors.d.ts +15 -0
  64. package/dist/discretize/discretize_errors.d.ts.map +1 -0
  65. package/dist/discretize/discretize_errors.js +30 -0
  66. package/dist/discretize/discretize_options.d.ts +18 -0
  67. package/dist/discretize/discretize_options.d.ts.map +1 -0
  68. package/dist/discretize/discretize_options.js +19 -0
  69. package/dist/discretize/discretize_types.d.ts +36 -0
  70. package/dist/discretize/discretize_types.d.ts.map +1 -0
  71. package/dist/discretize/discretize_types.js +1 -0
  72. package/dist/discretize/internal/curve_guards.d.ts +35 -0
  73. package/dist/discretize/internal/curve_guards.d.ts.map +1 -0
  74. package/dist/discretize/internal/curve_guards.js +62 -0
  75. package/dist/discretize/internal/postprocess.d.ts +5 -0
  76. package/dist/discretize/internal/postprocess.d.ts.map +1 -0
  77. package/dist/discretize/internal/postprocess.js +109 -0
  78. package/dist/discretize/internal/sampling_utils.d.ts +8 -0
  79. package/dist/discretize/internal/sampling_utils.d.ts.map +1 -0
  80. package/dist/discretize/internal/sampling_utils.js +36 -0
  81. package/dist/discretize/register_builtin_strategies.d.ts +3 -0
  82. package/dist/discretize/register_builtin_strategies.d.ts.map +1 -0
  83. package/dist/discretize/register_builtin_strategies.js +10 -0
  84. package/dist/discretize/strategies/bspline_strategy.d.ts +4 -0
  85. package/dist/discretize/strategies/bspline_strategy.d.ts.map +1 -0
  86. package/dist/discretize/strategies/bspline_strategy.js +115 -0
  87. package/dist/discretize/strategies/circle_strategy.d.ts +7 -0
  88. package/dist/discretize/strategies/circle_strategy.d.ts.map +1 -0
  89. package/dist/discretize/strategies/circle_strategy.js +55 -0
  90. package/dist/discretize/strategies/ellipse_strategy.d.ts +7 -0
  91. package/dist/discretize/strategies/ellipse_strategy.d.ts.map +1 -0
  92. package/dist/discretize/strategies/ellipse_strategy.js +86 -0
  93. package/dist/discretize/strategies/line_strategy.d.ts +4 -0
  94. package/dist/discretize/strategies/line_strategy.d.ts.map +1 -0
  95. package/dist/discretize/strategies/line_strategy.js +40 -0
  96. package/dist/discretize/strategy_registry.d.ts +9 -0
  97. package/dist/discretize/strategy_registry.d.ts.map +1 -0
  98. package/dist/discretize/strategy_registry.js +34 -0
  99. package/dist/index.d.ts +30 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +24 -0
  102. package/dist/intersections/analytic_x_algorithm.d.ts +10 -0
  103. package/dist/intersections/analytic_x_algorithm.d.ts.map +1 -0
  104. package/dist/intersections/analytic_x_algorithm.js +83 -0
  105. package/dist/intersections/curve_x_engine.d.ts +9 -0
  106. package/dist/intersections/curve_x_engine.d.ts.map +1 -0
  107. package/dist/intersections/curve_x_engine.js +27 -0
  108. package/dist/intersections/index.d.ts +5 -0
  109. package/dist/intersections/index.d.ts.map +1 -0
  110. package/dist/intersections/index.js +11 -0
  111. package/dist/intersections/internal/certification.d.ts +34 -0
  112. package/dist/intersections/internal/certification.d.ts.map +1 -0
  113. package/dist/intersections/internal/certification.js +238 -0
  114. package/dist/intersections/internal/interval_clipping.d.ts +29 -0
  115. package/dist/intersections/internal/interval_clipping.d.ts.map +1 -0
  116. package/dist/intersections/internal/interval_clipping.js +123 -0
  117. package/dist/intersections/internal/kind.d.ts +4 -0
  118. package/dist/intersections/internal/kind.d.ts.map +1 -0
  119. package/dist/intersections/internal/kind.js +16 -0
  120. package/dist/intersections/internal/pair.d.ts +9 -0
  121. package/dist/intersections/internal/pair.d.ts.map +1 -0
  122. package/dist/intersections/internal/pair.js +14 -0
  123. package/dist/intersections/internal/result.d.ts +20 -0
  124. package/dist/intersections/internal/result.d.ts.map +1 -0
  125. package/dist/intersections/internal/result.js +125 -0
  126. package/dist/intersections/internal/sampling.d.ts +15 -0
  127. package/dist/intersections/internal/sampling.d.ts.map +1 -0
  128. package/dist/intersections/internal/sampling.js +131 -0
  129. package/dist/intersections/internal/segment.d.ts +32 -0
  130. package/dist/intersections/internal/segment.d.ts.map +1 -0
  131. package/dist/intersections/internal/segment.js +137 -0
  132. package/dist/intersections/internal/tolerance.d.ts +10 -0
  133. package/dist/intersections/internal/tolerance.d.ts.map +1 -0
  134. package/dist/intersections/internal/tolerance.js +20 -0
  135. package/dist/intersections/intersector.d.ts +6 -0
  136. package/dist/intersections/intersector.d.ts.map +1 -0
  137. package/dist/intersections/intersector.js +1 -0
  138. package/dist/intersections/numeric_x_algorithm.d.ts +10 -0
  139. package/dist/intersections/numeric_x_algorithm.d.ts.map +1 -0
  140. package/dist/intersections/numeric_x_algorithm.js +73 -0
  141. package/dist/intersections/solvers/bspline_self_solver.d.ts +7 -0
  142. package/dist/intersections/solvers/bspline_self_solver.d.ts.map +1 -0
  143. package/dist/intersections/solvers/bspline_self_solver.js +308 -0
  144. package/dist/intersections/solvers/line_line_pair_solver.d.ts +7 -0
  145. package/dist/intersections/solvers/line_line_pair_solver.d.ts.map +1 -0
  146. package/dist/intersections/solvers/line_line_pair_solver.js +35 -0
  147. package/dist/intersections/solvers/pair_solvers.d.ts +94 -0
  148. package/dist/intersections/solvers/pair_solvers.d.ts.map +1 -0
  149. package/dist/intersections/solvers/pair_solvers.js +1078 -0
  150. package/dist/intersections/solvers/polyline_pair_intersector.d.ts +51 -0
  151. package/dist/intersections/solvers/polyline_pair_intersector.d.ts.map +1 -0
  152. package/dist/intersections/solvers/polyline_pair_intersector.js +731 -0
  153. package/dist/intersections/types.d.ts +11 -0
  154. package/dist/intersections/types.d.ts.map +1 -0
  155. package/dist/intersections/types.js +1 -0
  156. package/dist/serialize/dump_types.d.ts +101 -0
  157. package/dist/serialize/dump_types.d.ts.map +1 -0
  158. package/dist/serialize/dump_types.js +5 -0
  159. package/dist/serialize/geom_mgr.d.ts +24 -0
  160. package/dist/serialize/geom_mgr.d.ts.map +1 -0
  161. package/dist/serialize/geom_mgr.js +30 -0
  162. package/dist/types/type_define.d.ts +29 -0
  163. package/dist/types/type_define.d.ts.map +1 -0
  164. package/dist/types/type_define.js +10 -0
  165. package/dist/types/type_guard.d.ts +46 -0
  166. package/dist/types/type_guard.d.ts.map +1 -0
  167. package/dist/types/type_guard.js +5 -0
  168. package/dist/utils/math_error.d.ts +16 -0
  169. package/dist/utils/math_error.d.ts.map +1 -0
  170. package/dist/utils/math_error.js +35 -0
  171. package/dist/utils/math_utils.d.ts +9 -0
  172. package/dist/utils/math_utils.d.ts.map +1 -0
  173. package/dist/utils/math_utils.js +25 -0
  174. package/dist/utils/precision.d.ts +29 -0
  175. package/dist/utils/precision.d.ts.map +1 -0
  176. package/dist/utils/precision.js +44 -0
  177. package/package.json +38 -0
@@ -0,0 +1,793 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var BSpline2_1;
8
+ import { EN_GEO_TYPE } from '../constants/geom_type';
9
+ import { Box2 } from '../core/box2';
10
+ import { Vec2 } from '../core/vec2';
11
+ import { RegisterGeom } from '../serialize/geom_mgr';
12
+ import { Axis2D } from '../types/type_define';
13
+ import { MathError } from '../utils/math_error';
14
+ import { Precision } from '../utils/precision';
15
+ import { Curve2 } from './curve2';
16
+ import { Interval } from './interval';
17
+ let BSpline2 = BSpline2_1 = class BSpline2 extends Curve2 {
18
+ /**
19
+ * 构造 B 样条曲线。
20
+ * @param controlPoints 控制点数组。
21
+ * @param degree 样条次数(`>=1`)。
22
+ * @param options 节点、权重与周期配置。
23
+ */
24
+ constructor(controlPoints, degree, options = {}) {
25
+ super();
26
+ MathError.assert(Number.isInteger(degree) && degree >= 1, 'BSpline2: degree must be an integer >= 1');
27
+ MathError.assert(controlPoints.length >= degree + 1, 'BSpline2: controlPoints.length must be >= degree + 1');
28
+ MathError.assert(options.isPeriodic !== true, 'BSpline2: periodic is not supported in v1');
29
+ this._degree = degree;
30
+ this._controlPoints = controlPoints.map((p) => p.clone());
31
+ this.assertFiniteControlPoints(this._controlPoints);
32
+ this._weights = this.resolveWeights(options.weights);
33
+ this._knots = this.resolveKnots(options);
34
+ this.validateExpandedKnots(this._knots, this._controlPoints.length, degree);
35
+ const domainStart = this._knots[this._degree];
36
+ const domainEnd = this._knots[this._knots.length - this._degree - 1];
37
+ MathError.assert(domainEnd > domainStart, 'BSpline2: invalid parameter domain');
38
+ this.setRange(new Interval(domainStart, domainEnd));
39
+ }
40
+ /** 控制点数组(返回深拷贝) */
41
+ get controlPoints() {
42
+ return this._controlPoints.map((p) => p.clone());
43
+ }
44
+ /** 样条次数 */
45
+ get degree() {
46
+ return this._degree;
47
+ }
48
+ /** 展开节点向量(返回副本) */
49
+ get expandedKnots() {
50
+ return [...this._knots];
51
+ }
52
+ /** 权重数组(返回副本) */
53
+ get weights() {
54
+ return [...this._weights];
55
+ }
56
+ /**
57
+ * 获取参数域内部的连续性断点参数。
58
+ * 当 knot 重数 >= degree 时,可视为连续性下降断点。
59
+ */
60
+ getContinuityBreakParams(eps = Precision.CURVE_PARAM_EPS) {
61
+ const breaks = [];
62
+ const range = this._range;
63
+ for (let i = 0; i < this._knots.length;) {
64
+ const knot = this._knots[i];
65
+ let multiplicity = 1;
66
+ i++;
67
+ while (i < this._knots.length && Math.abs(this._knots[i] - knot) <= eps) {
68
+ multiplicity++;
69
+ i++;
70
+ }
71
+ if (multiplicity >= this._degree && knot > range.start + eps && knot < range.end - eps) {
72
+ breaks.push(knot);
73
+ }
74
+ }
75
+ return breaks;
76
+ }
77
+ pointAt(u) {
78
+ return this.derivatives(u, 0)[0];
79
+ }
80
+ tangentAt(u) {
81
+ return this.derivativeAt(u, 1);
82
+ }
83
+ derivatives(u, n) {
84
+ MathError.assert(Number.isInteger(n) && n >= 0, 'BSpline2.derivatives: n must be a non-negative integer');
85
+ const uu = this.snapParam(u);
86
+ const p = this._degree;
87
+ const du = Math.min(n, p);
88
+ const span = BSpline2_1.findSpan(this._controlPoints.length - 1, p, uu, this._knots);
89
+ const ders = BSpline2_1.basisFunctionDerivatives(span, uu, p, du, this._knots);
90
+ const pw = this.homogeneousControlPoints();
91
+ const ckw = [];
92
+ for (let k = 0; k <= du; k++) {
93
+ let x = 0;
94
+ let y = 0;
95
+ let w = 0;
96
+ for (let j = 0; j <= p; j++) {
97
+ const idx = span - p + j;
98
+ const coeff = ders[k][j];
99
+ x += coeff * pw[idx].x;
100
+ y += coeff * pw[idx].y;
101
+ w += coeff * pw[idx].w;
102
+ }
103
+ ckw.push({ x, y, w });
104
+ }
105
+ MathError.assert(Math.abs(ckw[0].w) > Precision.CURVE_NEWTON_EPS, 'BSpline2.derivatives: rational weight is degenerate');
106
+ const ret = [];
107
+ for (let k = 0; k <= du; k++) {
108
+ let vx = ckw[k].x;
109
+ let vy = ckw[k].y;
110
+ for (let i = 1; i <= k; i++) {
111
+ const b = BSpline2_1.binomial(k, i) * ckw[i].w;
112
+ vx -= b * ret[k - i].x;
113
+ vy -= b * ret[k - i].y;
114
+ }
115
+ ret.push(new Vec2(vx / ckw[0].w, vy / ckw[0].w));
116
+ }
117
+ for (let k = du + 1; k <= n; k++) {
118
+ ret.push(Vec2.zero());
119
+ }
120
+ return ret;
121
+ }
122
+ curvatureAt(u) {
123
+ const d1 = this.derivativeAt(u, 1);
124
+ const d2 = this.derivativeAt(u, 2);
125
+ const denom = Math.pow(d1.lenSq(), 1.5);
126
+ MathError.assert(denom > Precision.CURVE_NEWTON_EPS, 'BSpline2.curvatureAt: tangent is degenerate');
127
+ return Math.abs(d1.cross(d2)) / denom;
128
+ }
129
+ length(range) {
130
+ if (!range) {
131
+ return this.integrateLength(this._range.start, this._range.end);
132
+ }
133
+ this._range.assertContainsRange(range, Precision.CURVE_PARAM_EPS);
134
+ return this.integrateLength(range.start, range.end);
135
+ }
136
+ lengthAtParam(u) {
137
+ const uu = this.snapParam(u);
138
+ return this.integrateLength(this._range.start, uu);
139
+ }
140
+ paramAtLength(s, tol = Precision.CURVE_LENGTH_EPS) {
141
+ MathError.assert(Number.isFinite(tol) && tol > 0, 'BSpline2.paramAtLength: tol must be > 0');
142
+ const total = this.length();
143
+ const start = this._range.start;
144
+ const end = this._range.end;
145
+ MathError.assert(s >= -tol && s <= total + tol, `BSpline2.paramAtLength: s out of range [0, ${total}]`);
146
+ if (s <= tol)
147
+ return start;
148
+ if (total - s <= tol)
149
+ return end;
150
+ const target = Math.min(total, Math.max(0, s));
151
+ // 使用基类统一的“Newton + 二分”模板,保证边界与收敛行为一致。
152
+ return this.solveParamByHybridNewton(target, start, end, tol, (u) => this.integrateLength(start, u), (u) => this.tangentAt(u).len(), 'BSpline2.paramAtLength: failed to converge', start + (target / total) * (end - start));
153
+ }
154
+ split(u) {
155
+ const splitParts = this._range.split(u, Precision.CURVE_PARAM_EPS);
156
+ if (splitParts.length === 0)
157
+ return [];
158
+ const uu = this.snapParam(u);
159
+ const p = this._degree;
160
+ const U = [...this._knots];
161
+ const n = this._controlPoints.length - 1;
162
+ const k = BSpline2_1.findSpan(n, p, uu, U);
163
+ const s = BSpline2_1.knotMultiplicity(uu, U);
164
+ let pw = this.homogeneousControlPoints();
165
+ let knots = U;
166
+ const r = p - s;
167
+ for (let i = 0; i < r; i++) {
168
+ const inserted = BSpline2_1.insertKnotOnce(pw, knots, p, uu);
169
+ pw = inserted.points;
170
+ knots = inserted.knots;
171
+ }
172
+ const leftEnd = k - s;
173
+ const leftPw = pw.slice(0, leftEnd + 1);
174
+ const rightPw = pw.slice(leftEnd);
175
+ const leftKnots = [...U.slice(0, k + 1), ...new Array(p - s + 1).fill(uu)];
176
+ const rightKnots = [...new Array(p + 1).fill(uu), ...U.slice(k + 1)];
177
+ const left = BSpline2_1.fromHomogeneous(leftPw, p, leftKnots);
178
+ const right = BSpline2_1.fromHomogeneous(rightPw, p, rightKnots);
179
+ const ret = [left, right].filter((c) => c.length() > Precision.CURVE_LENGTH_EPS);
180
+ return ret;
181
+ }
182
+ trim(range) {
183
+ this._range.assertContainsRange(range, Precision.CURVE_PARAM_EPS);
184
+ if (range.length() <= Precision.CURVE_PARAM_EPS)
185
+ return [];
186
+ const fullStart = this._range.start;
187
+ const fullEnd = this._range.end;
188
+ let cur = this.clone();
189
+ if (range.end < fullEnd - Precision.CURVE_PARAM_EPS) {
190
+ const s = cur.split(range.end);
191
+ MathError.assert(s.length > 0, 'BSpline2.trim: split at end failed');
192
+ cur = s[0];
193
+ }
194
+ if (range.start > fullStart + Precision.CURVE_PARAM_EPS) {
195
+ const s = cur.split(range.start);
196
+ MathError.assert(s.length > 0, 'BSpline2.trim: split at start failed');
197
+ cur = s[s.length - 1];
198
+ }
199
+ return cur.length() > Precision.CURVE_LENGTH_EPS ? [cur] : [];
200
+ }
201
+ reverse() {
202
+ const m = this._knots.length - 1;
203
+ const start = this._range.start;
204
+ const end = this._range.end;
205
+ this._controlPoints.reverse();
206
+ this._weights.reverse();
207
+ const nextKnots = new Array(this._knots.length);
208
+ for (let i = 0; i <= m; i++) {
209
+ nextKnots[i] = start + end - this._knots[m - i];
210
+ }
211
+ this._knots = nextKnots;
212
+ this.setRange(new Interval(this._knots[this._degree], this._knots[this._knots.length - this._degree - 1]));
213
+ return this;
214
+ }
215
+ transform(m) {
216
+ const next = this._controlPoints.map((p) => m.transformedPoint(p));
217
+ this.assertFiniteControlPoints(next);
218
+ this._controlPoints = next;
219
+ return this;
220
+ }
221
+ transformed(m) {
222
+ return this.clone().transform(m);
223
+ }
224
+ closestPoint(p, tol = Precision.CURVE_LENGTH_EPS) {
225
+ MathError.assert(Number.isFinite(tol) && tol > 0, 'BSpline2.closestPoint: tol must be > 0');
226
+ // 先采样粗定位,再用 Newton 在局部窗口细化,避免直接迭代落入错误极值点。
227
+ return this.solveClosestPointBySampleNewton(p, tol, 96, (u) => this.pointAt(u), (u) => this.derivativeAt(u, 1), (u) => this.derivativeAt(u, 2), 'BSpline2.closestPoint: failed to converge');
228
+ }
229
+ boundingBox(accurate = false) {
230
+ const controlBox = Box2.fromPoints(this._controlPoints);
231
+ if (!accurate)
232
+ return controlBox;
233
+ const range = this.getRange();
234
+ const spanBounds = this.buildBBoxSpanBounds();
235
+ const candidates = [
236
+ this.pointAt(range.start),
237
+ this.pointAt(range.end),
238
+ ];
239
+ for (const [u0, u1] of spanBounds) {
240
+ const xRoots = this.solveComponentExtremaInSpan(Axis2D.X, u0, u1);
241
+ const yRoots = this.solveComponentExtremaInSpan(Axis2D.Y, u0, u1);
242
+ for (const u of [...xRoots, ...yRoots]) {
243
+ if (!range.contains(u, Precision.CURVE_PARAM_EPS))
244
+ continue;
245
+ candidates.push(this.pointAt(u));
246
+ }
247
+ }
248
+ if (candidates.length < 2)
249
+ return controlBox;
250
+ const tightBox = Box2.fromPoints(candidates);
251
+ return this.expandBoxBySpanSamples(tightBox, spanBounds);
252
+ }
253
+ isValid(eps = Precision.CURVE_LENGTH_EPS) {
254
+ if (!Number.isInteger(this._degree) || this._degree < 1)
255
+ return false;
256
+ if (this._controlPoints.length < this._degree + 1)
257
+ return false;
258
+ if (this._weights.length !== this._controlPoints.length)
259
+ return false;
260
+ if (this._weights.some((w) => !Number.isFinite(w) || w <= eps))
261
+ return false;
262
+ if (this._knots.length !== this._controlPoints.length + this._degree + 1)
263
+ return false;
264
+ for (let i = 1; i < this._knots.length; i++) {
265
+ if (this._knots[i] < this._knots[i - 1])
266
+ return false;
267
+ }
268
+ const domainStart = this._knots[this._degree];
269
+ const domainEnd = this._knots[this._knots.length - this._degree - 1];
270
+ return Number.isFinite(domainStart) && Number.isFinite(domainEnd) && domainEnd - domainStart > eps;
271
+ }
272
+ isBSpline() {
273
+ return true;
274
+ }
275
+ /**
276
+ * 结构等价判断(字段级)。
277
+ * @param other 对比样条。
278
+ * @param eps 数值容差。
279
+ * @returns 阶次、控制点、权重与节点向量逐项近似相等时返回 `true`。
280
+ */
281
+ equals(other, eps = Precision.EPS) {
282
+ if (this._degree !== other._degree)
283
+ return false;
284
+ if (this._controlPoints.length !== other._controlPoints.length)
285
+ return false;
286
+ if (this._weights.length !== other._weights.length)
287
+ return false;
288
+ if (this._knots.length !== other._knots.length)
289
+ return false;
290
+ for (let i = 0; i < this._controlPoints.length; i++) {
291
+ if (!this._controlPoints[i].equals(other._controlPoints[i], eps))
292
+ return false;
293
+ if (!Precision.equal(this._weights[i], other._weights[i], eps))
294
+ return false;
295
+ }
296
+ for (let i = 0; i < this._knots.length; i++) {
297
+ if (!Precision.equal(this._knots[i], other._knots[i], eps))
298
+ return false;
299
+ }
300
+ return true;
301
+ }
302
+ clone() {
303
+ return new BSpline2_1(this._controlPoints, this._degree, {
304
+ expandedKnots: this._knots,
305
+ weights: this._weights,
306
+ isPeriodic: false,
307
+ });
308
+ }
309
+ dump() {
310
+ return {
311
+ type: BSpline2_1.type,
312
+ controlPoints: this._controlPoints.map((p) => ({ x: p.x, y: p.y })),
313
+ degree: this._degree,
314
+ expandedKnots: [...this._knots],
315
+ weights: [...this._weights],
316
+ isPeriodic: false,
317
+ };
318
+ }
319
+ static load(data) {
320
+ return new BSpline2_1(data.controlPoints.map((p) => new Vec2(p.x, p.y)), data.degree, {
321
+ expandedKnots: data.expandedKnots,
322
+ weights: data.weights,
323
+ isPeriodic: data.isPeriodic,
324
+ });
325
+ }
326
+ /**
327
+ * 解析并校验权重。
328
+ * @param weights 输入权重;未传时返回全 1。
329
+ */
330
+ resolveWeights(weights) {
331
+ if (!weights) {
332
+ return new Array(this._controlPoints.length).fill(1);
333
+ }
334
+ MathError.assert(weights.length === this._controlPoints.length, 'BSpline2: weights.length must equal controlPoints.length');
335
+ const w = [...weights];
336
+ for (const wi of w) {
337
+ MathError.assert(Number.isFinite(wi) && wi > 0, 'BSpline2: weight must be > 0');
338
+ }
339
+ return w;
340
+ }
341
+ /**
342
+ * 解析并统一节点输入。
343
+ * @param options 构造选项。
344
+ * @returns 统一后的 expanded knots。
345
+ */
346
+ resolveKnots(options) {
347
+ const fromExpanded = options.expandedKnots ? [...options.expandedKnots] : undefined;
348
+ const fromCompact = options.knots && options.multiplicities
349
+ ? BSpline2_1.expandKnots(options.knots, options.multiplicities)
350
+ : undefined;
351
+ const resolved = fromExpanded ?? fromCompact;
352
+ if (!resolved) {
353
+ MathError.throw('BSpline2: expandedKnots or (knots+multiplicities) is required');
354
+ }
355
+ if (fromExpanded && fromCompact) {
356
+ MathError.assert(fromExpanded.length === fromCompact.length, 'BSpline2: expandedKnots mismatch with knots+multiplicities');
357
+ for (let i = 0; i < fromExpanded.length; i++) {
358
+ MathError.assert(Precision.equal(fromExpanded[i], fromCompact[i], Precision.CURVE_PARAM_EPS), 'BSpline2: expandedKnots mismatch with knots+multiplicities');
359
+ }
360
+ }
361
+ return resolved;
362
+ }
363
+ /**
364
+ * 校验 expanded knots 基本合法性。
365
+ * @param knots 展开节点向量。
366
+ * @param controlPointCount 控制点数量。
367
+ * @param degree 样条次数。
368
+ */
369
+ validateExpandedKnots(knots, controlPointCount, degree) {
370
+ MathError.assert(knots.length === controlPointCount + degree + 1, 'BSpline2: invalid expandedKnots length');
371
+ for (const k of knots) {
372
+ MathError.assert(Number.isFinite(k), 'BSpline2: knot must be finite');
373
+ }
374
+ for (let i = 1; i < knots.length; i++) {
375
+ MathError.assert(knots[i] >= knots[i - 1], 'BSpline2: knots must be non-decreasing');
376
+ }
377
+ }
378
+ /**
379
+ * 校验控制点坐标有限性。
380
+ * @param points 控制点数组。
381
+ */
382
+ assertFiniteControlPoints(points) {
383
+ for (const p of points) {
384
+ MathError.assert(Number.isFinite(p.x) && Number.isFinite(p.y), 'BSpline2: control point must be finite');
385
+ }
386
+ }
387
+ /**
388
+ * 转换为齐次控制点。
389
+ * @returns 齐次点数组 `{x,y,w}`。
390
+ */
391
+ homogeneousControlPoints() {
392
+ const ret = [];
393
+ for (let i = 0; i < this._controlPoints.length; i++) {
394
+ const w = this._weights[i];
395
+ const p = this._controlPoints[i];
396
+ ret.push({ x: p.x * w, y: p.y * w, w });
397
+ }
398
+ return ret;
399
+ }
400
+ /**
401
+ * 自适应积分计算弧长。
402
+ * @param u0 起始参数。
403
+ * @param u1 结束参数。
404
+ * @param depth 当前递归深度。
405
+ */
406
+ integrateLength(u0, u1, depth = 0) {
407
+ if (u1 < u0)
408
+ return 0;
409
+ const f = (u) => this.tangentAt(u).len();
410
+ const whole = this.gaussLegendre5(f, u0, u1);
411
+ if (depth >= Precision.CURVE_INTEGRAL_MAX_DEPTH) {
412
+ return whole;
413
+ }
414
+ const mid = (u0 + u1) * 0.5;
415
+ const left = this.gaussLegendre5(f, u0, mid);
416
+ const right = this.gaussLegendre5(f, mid, u1);
417
+ const err = Math.abs((left + right) - whole);
418
+ if (err <= Precision.CURVE_LENGTH_EPS) {
419
+ return left + right;
420
+ }
421
+ return this.integrateLength(u0, mid, depth + 1) + this.integrateLength(mid, u1, depth + 1);
422
+ }
423
+ gaussLegendre5(f, a, b) {
424
+ const nodes = [
425
+ 0,
426
+ -0.5384693101056831,
427
+ 0.5384693101056831,
428
+ -0.906179845938664,
429
+ 0.906179845938664,
430
+ ];
431
+ const weights = [
432
+ 0.5688888888888889,
433
+ 0.47862867049936647,
434
+ 0.47862867049936647,
435
+ 0.23692688505618908,
436
+ 0.23692688505618908,
437
+ ];
438
+ const c1 = (b - a) * 0.5;
439
+ const c2 = (b + a) * 0.5;
440
+ let sum = 0;
441
+ for (let i = 0; i < nodes.length; i++) {
442
+ sum += weights[i] * f(c1 * nodes[i] + c2);
443
+ }
444
+ return c1 * sum;
445
+ }
446
+ buildBBoxSpanBounds() {
447
+ const range = this.getRange();
448
+ const boundaries = [range.start, range.end, ...this.getUniqueKnotsInRange(range.start, range.end)];
449
+ const sorted = [...new Set(boundaries)]
450
+ .filter((u) => Number.isFinite(u) && range.contains(u, Precision.CURVE_PARAM_EPS))
451
+ .sort((a, b) => a - b);
452
+ const spans = [];
453
+ for (let i = 0; i < sorted.length - 1; i++) {
454
+ const u0 = sorted[i];
455
+ const u1 = sorted[i + 1];
456
+ if (u1 - u0 <= Precision.CURVE_PARAM_EPS)
457
+ continue;
458
+ spans.push([u0, u1]);
459
+ }
460
+ return spans;
461
+ }
462
+ getUniqueKnotsInRange(start, end) {
463
+ const unique = [];
464
+ for (const knot of this._knots) {
465
+ if (knot <= start + Precision.CURVE_PARAM_EPS || knot >= end - Precision.CURVE_PARAM_EPS)
466
+ continue;
467
+ if (unique.length > 0 && Math.abs(unique[unique.length - 1] - knot) <= Precision.CURVE_PARAM_EPS)
468
+ continue;
469
+ unique.push(knot);
470
+ }
471
+ return unique;
472
+ }
473
+ solveComponentExtremaInSpan(axis, u0, u1) {
474
+ const roots = [];
475
+ const brackets = this.findRootBrackets(axis, u0, u1);
476
+ for (const [b0, b1] of brackets) {
477
+ const root = this.refineRootBracketedNewton(axis, b0, b1);
478
+ if (root === undefined)
479
+ continue;
480
+ const clamped = this.clampParamForBBox(root);
481
+ if (clamped < u0 - Precision.CURVE_PARAM_EPS || clamped > u1 + Precision.CURVE_PARAM_EPS)
482
+ continue;
483
+ if (roots.some((u) => Math.abs(u - clamped) <= Precision.CURVE_PARAM_EPS * 4))
484
+ continue;
485
+ roots.push(clamped);
486
+ }
487
+ return roots;
488
+ }
489
+ findRootBrackets(axis, u0, u1) {
490
+ const brackets = [];
491
+ const steps = this.bboxRootSampleCount();
492
+ const du = (u1 - u0) / steps;
493
+ let prevU = u0;
494
+ let prevF = this.componentDerivative(axis, prevU, 1);
495
+ for (let i = 1; i <= steps; i++) {
496
+ const curU = i === steps ? u1 : (u0 + du * i);
497
+ const curF = this.componentDerivative(axis, curU, 1);
498
+ if (Math.abs(prevF) <= Precision.CURVE_NEWTON_EPS) {
499
+ brackets.push([
500
+ Math.max(u0, prevU - du),
501
+ Math.min(u1, prevU + du),
502
+ ]);
503
+ }
504
+ if (Math.abs(curF) <= Precision.CURVE_NEWTON_EPS || prevF * curF <= 0) {
505
+ brackets.push([prevU, curU]);
506
+ }
507
+ prevU = curU;
508
+ prevF = curF;
509
+ }
510
+ return this.mergeBrackets(brackets, u0, u1);
511
+ }
512
+ refineRootBracketedNewton(axis, uL, uR) {
513
+ let lo = this.clampParamForBBox(Math.min(uL, uR));
514
+ let hi = this.clampParamForBBox(Math.max(uL, uR));
515
+ if (hi - lo <= Precision.CURVE_PARAM_EPS)
516
+ return undefined;
517
+ let fLo = this.componentDerivative(axis, lo, 1);
518
+ let fHi = this.componentDerivative(axis, hi, 1);
519
+ let u = (lo + hi) * 0.5;
520
+ for (let iter = 0; iter < Precision.CURVE_MAX_ITER; iter++) {
521
+ const f = this.componentDerivative(axis, u, 1);
522
+ if (Math.abs(f) <= Precision.CURVE_NEWTON_EPS)
523
+ return u;
524
+ const d2 = this.componentDerivative(axis, u, 2);
525
+ let next = Number.NaN;
526
+ if (Number.isFinite(d2) && Math.abs(d2) > Precision.CURVE_NEWTON_EPS) {
527
+ next = u - f / d2;
528
+ }
529
+ if (!Number.isFinite(next) || next <= lo || next >= hi) {
530
+ next = (lo + hi) * 0.5;
531
+ }
532
+ if (fLo * f <= 0) {
533
+ hi = u;
534
+ fHi = f;
535
+ }
536
+ else if (f * fHi <= 0) {
537
+ lo = u;
538
+ fLo = f;
539
+ }
540
+ else {
541
+ if (next < u)
542
+ hi = u;
543
+ else
544
+ lo = u;
545
+ fLo = this.componentDerivative(axis, lo, 1);
546
+ fHi = this.componentDerivative(axis, hi, 1);
547
+ }
548
+ if (hi - lo <= Precision.CURVE_PARAM_EPS) {
549
+ const mid = (lo + hi) * 0.5;
550
+ if (Math.abs(this.componentDerivative(axis, mid, 1)) <= Precision.CURVE_NEWTON_EPS * 10) {
551
+ return mid;
552
+ }
553
+ return undefined;
554
+ }
555
+ u = next;
556
+ }
557
+ return undefined;
558
+ }
559
+ mergeBrackets(brackets, u0, u1) {
560
+ if (brackets.length === 0)
561
+ return [];
562
+ const sorted = brackets
563
+ .map(([a, b]) => [this.clampParamForBBox(Math.min(a, b)), this.clampParamForBBox(Math.max(a, b))])
564
+ .filter(([a, b]) => b - a > Precision.CURVE_PARAM_EPS)
565
+ .sort((lhs, rhs) => lhs[0] - rhs[0] || lhs[1] - rhs[1]);
566
+ if (sorted.length === 0)
567
+ return [];
568
+ const merged = [sorted[0]];
569
+ for (let i = 1; i < sorted.length; i++) {
570
+ const cur = sorted[i];
571
+ const prev = merged[merged.length - 1];
572
+ if (cur[0] <= prev[1] + Precision.CURVE_PARAM_EPS) {
573
+ prev[1] = Math.max(prev[1], cur[1]);
574
+ continue;
575
+ }
576
+ merged.push(cur);
577
+ }
578
+ for (const item of merged) {
579
+ item[0] = Math.max(item[0], u0);
580
+ item[1] = Math.min(item[1], u1);
581
+ }
582
+ return merged.filter(([a, b]) => b - a > Precision.CURVE_PARAM_EPS);
583
+ }
584
+ bboxRootSampleCount() {
585
+ // Degree-scaled sampling keeps low-order curves fast while giving higher-order curves
586
+ // enough brackets for extrema detection without a single hard-coded global level.
587
+ return Math.max(8, Math.min(32, this._degree * 4));
588
+ }
589
+ expandBoxBySpanSamples(box, spans) {
590
+ let expanded = box;
591
+ const samplesPerSpan = Math.max(4, Math.floor(this.bboxRootSampleCount() / 2));
592
+ for (const [u0, u1] of spans) {
593
+ for (let i = 1; i < samplesPerSpan; i++) {
594
+ const t = i / samplesPerSpan;
595
+ const u = u0 + (u1 - u0) * t;
596
+ const p = this.pointAt(u);
597
+ if (!expanded.containsPoint(p)) {
598
+ expanded = expanded.expandByPoint(p);
599
+ }
600
+ }
601
+ }
602
+ return expanded;
603
+ }
604
+ componentDerivative(axis, u, order) {
605
+ const d = this.derivativeAt(this.clampParamForBBox(u), order);
606
+ return axis === Axis2D.X ? d.x : d.y;
607
+ }
608
+ clampParamForBBox(u) {
609
+ const range = this._range;
610
+ if (u <= range.start)
611
+ return range.start;
612
+ if (u >= range.end)
613
+ return range.end;
614
+ return u;
615
+ }
616
+ /**
617
+ * 参数吸附到端点,避免边界浮点抖动。
618
+ * @param u 输入参数。
619
+ * @returns 吸附后的参数。
620
+ */
621
+ snapParam(u) {
622
+ this._range.assertContains(u, Precision.CURVE_PARAM_EPS);
623
+ const start = this._range.start;
624
+ const end = this._range.end;
625
+ if (Math.abs(u - start) <= Precision.CURVE_PARAM_EPS)
626
+ return start;
627
+ if (Math.abs(u - end) <= Precision.CURVE_PARAM_EPS)
628
+ return end;
629
+ return u;
630
+ }
631
+ static fromHomogeneous(points, degree, knots) {
632
+ const cps = [];
633
+ const ws = [];
634
+ for (const p of points) {
635
+ MathError.assert(Math.abs(p.w) > Precision.CURVE_NEWTON_EPS, 'BSpline2.fromHomogeneous: invalid homogeneous weight');
636
+ cps.push(new Vec2(p.x / p.w, p.y / p.w));
637
+ ws.push(p.w);
638
+ }
639
+ return new BSpline2_1(cps, degree, { expandedKnots: knots, weights: ws, isPeriodic: false });
640
+ }
641
+ static expandKnots(knots, multiplicities) {
642
+ MathError.assert(knots.length === multiplicities.length, 'BSpline2: knots and multiplicities length mismatch');
643
+ const expanded = [];
644
+ for (let i = 0; i < knots.length; i++) {
645
+ const m = multiplicities[i];
646
+ MathError.assert(Number.isInteger(m) && m > 0, 'BSpline2: multiplicity must be positive integer');
647
+ for (let j = 0; j < m; j++)
648
+ expanded.push(knots[i]);
649
+ }
650
+ return expanded;
651
+ }
652
+ static findSpan(n, p, u, U) {
653
+ if (u >= U[n + 1] - Precision.CURVE_PARAM_EPS)
654
+ return n;
655
+ if (u <= U[p] + Precision.CURVE_PARAM_EPS)
656
+ return p;
657
+ let low = p;
658
+ let high = n + 1;
659
+ let mid = Math.floor((low + high) * 0.5);
660
+ while (u < U[mid] || u >= U[mid + 1]) {
661
+ if (u < U[mid])
662
+ high = mid;
663
+ else
664
+ low = mid;
665
+ mid = Math.floor((low + high) * 0.5);
666
+ }
667
+ return mid;
668
+ }
669
+ static knotMultiplicity(u, U) {
670
+ let s = 0;
671
+ for (const k of U) {
672
+ if (Precision.equal(k, u, Precision.CURVE_PARAM_EPS))
673
+ s++;
674
+ }
675
+ return s;
676
+ }
677
+ static insertKnotOnce(points, knots, degree, u) {
678
+ const n = points.length - 1;
679
+ const k = BSpline2_1.findSpan(n, degree, u, knots);
680
+ const s = BSpline2_1.knotMultiplicity(u, knots);
681
+ const outPoints = new Array(points.length + 1);
682
+ const outKnots = new Array(knots.length + 1);
683
+ for (let i = 0; i <= k; i++)
684
+ outKnots[i] = knots[i];
685
+ outKnots[k + 1] = u;
686
+ for (let i = k + 1; i < knots.length; i++)
687
+ outKnots[i + 1] = knots[i];
688
+ for (let i = 0; i <= k - degree; i++)
689
+ outPoints[i] = points[i];
690
+ for (let i = k - s; i <= n; i++)
691
+ outPoints[i + 1] = points[i];
692
+ for (let i = k - degree + 1; i <= k - s; i++) {
693
+ const denom = knots[i + degree] - knots[i];
694
+ const alpha = Math.abs(denom) <= Precision.CURVE_NEWTON_EPS ? 0 : (u - knots[i]) / denom;
695
+ const p0 = points[i - 1];
696
+ const p1 = points[i];
697
+ outPoints[i] = {
698
+ x: (1 - alpha) * p0.x + alpha * p1.x,
699
+ y: (1 - alpha) * p0.y + alpha * p1.y,
700
+ w: (1 - alpha) * p0.w + alpha * p1.w,
701
+ };
702
+ }
703
+ return { points: outPoints, knots: outKnots };
704
+ }
705
+ static basisFunctionDerivatives(span, u, p, n, U) {
706
+ const ndu = Array.from({ length: p + 1 }, () => new Array(p + 1).fill(0));
707
+ const left = new Array(p + 1).fill(0);
708
+ const right = new Array(p + 1).fill(0);
709
+ ndu[0][0] = 1;
710
+ for (let j = 1; j <= p; j++) {
711
+ left[j] = u - U[span + 1 - j];
712
+ right[j] = U[span + j] - u;
713
+ let saved = 0;
714
+ for (let r = 0; r < j; r++) {
715
+ ndu[j][r] = right[r + 1] + left[j - r];
716
+ const denom = ndu[j][r];
717
+ const temp = Math.abs(denom) <= Precision.CURVE_NEWTON_EPS ? 0 : ndu[r][j - 1] / denom;
718
+ ndu[r][j] = saved + right[r + 1] * temp;
719
+ saved = left[j - r] * temp;
720
+ }
721
+ ndu[j][j] = saved;
722
+ }
723
+ const ders = Array.from({ length: n + 1 }, () => new Array(p + 1).fill(0));
724
+ for (let j = 0; j <= p; j++) {
725
+ ders[0][j] = ndu[j][p];
726
+ }
727
+ const a = [new Array(p + 1).fill(0), new Array(p + 1).fill(0)];
728
+ for (let r = 0; r <= p; r++) {
729
+ let s1 = 0;
730
+ let s2 = 1;
731
+ a[0][0] = 1;
732
+ for (let k = 1; k <= n; k++) {
733
+ let d = 0;
734
+ const rk = r - k;
735
+ const pk = p - k;
736
+ if (r >= k) {
737
+ const denom = ndu[pk + 1][rk];
738
+ a[s2][0] = Math.abs(denom) <= Precision.CURVE_NEWTON_EPS ? 0 : a[s1][0] / denom;
739
+ d = a[s2][0] * ndu[rk][pk];
740
+ }
741
+ const j1 = rk >= -1 ? 1 : -rk;
742
+ const j2 = (r - 1 <= pk) ? (k - 1) : (p - r);
743
+ for (let j = j1; j <= j2; j++) {
744
+ const denom = ndu[pk + 1][rk + j];
745
+ a[s2][j] = Math.abs(denom) <= Precision.CURVE_NEWTON_EPS
746
+ ? 0
747
+ : (a[s1][j] - a[s1][j - 1]) / denom;
748
+ d += a[s2][j] * ndu[rk + j][pk];
749
+ }
750
+ if (r <= pk) {
751
+ const denom = ndu[pk + 1][r];
752
+ a[s2][k] = Math.abs(denom) <= Precision.CURVE_NEWTON_EPS ? 0 : -a[s1][k - 1] / denom;
753
+ d += a[s2][k] * ndu[r][pk];
754
+ }
755
+ ders[k][r] = d;
756
+ const tmp = s1;
757
+ s1 = s2;
758
+ s2 = tmp;
759
+ }
760
+ }
761
+ let scale = p;
762
+ for (let k = 1; k <= n; k++) {
763
+ for (let j = 0; j <= p; j++) {
764
+ ders[k][j] *= scale;
765
+ }
766
+ scale *= (p - k);
767
+ }
768
+ return ders;
769
+ }
770
+ static binomial(n, k) {
771
+ if (k < 0 || k > n)
772
+ return 0;
773
+ if (k === 0 || k === n)
774
+ return 1;
775
+ let kk = k;
776
+ if (kk > n - kk)
777
+ kk = n - kk;
778
+ let result = 1;
779
+ for (let i = 1; i <= kk; i++) {
780
+ result = (result * (n - kk + i)) / i;
781
+ }
782
+ return result;
783
+ }
784
+ };
785
+ BSpline2.type = EN_GEO_TYPE.BSpline2;
786
+ BSpline2 = BSpline2_1 = __decorate([
787
+ RegisterGeom
788
+ /**
789
+ * 二维有理 B 样条(NURBS)曲线。
790
+ * 内部统一使用 expanded knot vector 存储与计算。
791
+ */
792
+ ], BSpline2);
793
+ export { BSpline2 };