@woosh/meep-engine 2.135.1 → 2.138.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 (110) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/2d/aabb/aabb2_intersects_ray.d.ts.map +1 -1
  3. package/src/core/geom/2d/aabb/aabb2_intersects_ray.js +5 -7
  4. package/src/core/geom/2d/hash-grid/shg_query_elements_circle.d.ts.map +1 -1
  5. package/src/core/geom/2d/hash-grid/shg_query_elements_circle.js +2 -3
  6. package/src/core/geom/3d/aabb/aabb3_intersects_line_segment.d.ts.map +1 -1
  7. package/src/core/geom/3d/aabb/aabb3_intersects_line_segment.js +9 -11
  8. package/src/core/geom/3d/tetrahedra/tetrahedron_compute_circumsphere.d.ts.map +1 -1
  9. package/src/core/geom/3d/tetrahedra/tetrahedron_compute_circumsphere.js +6 -4
  10. package/src/core/geom/3d/topology/simplify/quadratic/Quadric3.d.ts.map +1 -1
  11. package/src/core/geom/3d/topology/simplify/quadratic/Quadric3.js +1 -2
  12. package/src/core/geom/packing/miniball/Miniball.d.ts.map +1 -1
  13. package/src/core/geom/packing/miniball/Miniball.js +4 -11
  14. package/src/core/geom/packing/miniball/Subspan.d.ts +14 -39
  15. package/src/core/geom/packing/miniball/Subspan.d.ts.map +1 -1
  16. package/src/core/geom/packing/miniball/Subspan.js +105 -158
  17. package/src/core/geom/packing/miniball/miniball_compute_quality.d.ts.map +1 -1
  18. package/src/core/geom/packing/miniball/miniball_compute_quality.js +2 -6
  19. package/src/core/geom/vec/vector_axpy.d.ts +12 -0
  20. package/src/core/geom/vec/vector_axpy.d.ts.map +1 -0
  21. package/src/core/geom/vec/vector_axpy.js +15 -0
  22. package/src/core/geom/vec/vector_axpy_offset.d.ts +17 -0
  23. package/src/core/geom/vec/vector_axpy_offset.d.ts.map +1 -0
  24. package/src/core/geom/vec/vector_axpy_offset.js +20 -0
  25. package/src/core/geom/vec/vector_dot_offset.d.ts +18 -0
  26. package/src/core/geom/vec/vector_dot_offset.d.ts.map +1 -0
  27. package/src/core/geom/vec/vector_dot_offset.js +25 -0
  28. package/src/core/math/complex/complex_horner_eval.d.ts +2 -2
  29. package/src/core/math/complex/complex_horner_eval.d.ts.map +1 -1
  30. package/src/core/math/complex/complex_horner_eval.js +4 -1
  31. package/src/core/math/fabsf.d.ts +2 -3
  32. package/src/core/math/fabsf.d.ts.map +1 -1
  33. package/src/core/math/fabsf.js +3 -5
  34. package/src/core/math/linalg/cubic_residual_times_derivative_accumulate.d.ts +26 -0
  35. package/src/core/math/linalg/cubic_residual_times_derivative_accumulate.d.ts.map +1 -0
  36. package/src/core/math/linalg/cubic_residual_times_derivative_accumulate.js +43 -0
  37. package/src/core/math/linalg/eigen/matrix_eigenvalues_in_place.d.ts +2 -1
  38. package/src/core/math/linalg/eigen/matrix_eigenvalues_in_place.d.ts.map +1 -1
  39. package/src/core/math/linalg/eigen/matrix_eigenvalues_in_place.js +2 -1
  40. package/src/core/math/linalg/eigen/matrix_householder_in_place.d.ts +10 -4
  41. package/src/core/math/linalg/eigen/matrix_householder_in_place.d.ts.map +1 -1
  42. package/src/core/math/linalg/eigen/matrix_householder_in_place.js +122 -98
  43. package/src/core/math/linalg/eigen/matrix_qr_in_place.d.ts +2 -2
  44. package/src/core/math/linalg/eigen/matrix_qr_in_place.d.ts.map +1 -1
  45. package/src/core/math/linalg/eigen/matrix_qr_in_place.js +144 -124
  46. package/src/core/math/linalg/givens/givens_apply_rows.d.ts +22 -0
  47. package/src/core/math/linalg/givens/givens_apply_rows.d.ts.map +1 -0
  48. package/src/core/math/linalg/givens/givens_apply_rows.js +28 -0
  49. package/src/core/math/linalg/givens/givens_apply_rows_offset.d.ts +24 -0
  50. package/src/core/math/linalg/givens/givens_apply_rows_offset.d.ts.map +1 -0
  51. package/src/core/math/linalg/givens/givens_apply_rows_offset.js +30 -0
  52. package/src/core/math/linalg/givens/givens_rotation_coefficients.d.ts +25 -0
  53. package/src/core/math/linalg/givens/givens_rotation_coefficients.d.ts.map +1 -0
  54. package/src/core/math/linalg/givens/givens_rotation_coefficients.js +39 -0
  55. package/src/core/math/linalg/lu_factor_linear_system.d.ts +1 -1
  56. package/src/core/math/linalg/lu_factor_linear_system.d.ts.map +1 -1
  57. package/src/core/math/linalg/lu_factor_linear_system.js +108 -117
  58. package/src/core/math/linalg/lu_solve_linear_system.d.ts +1 -1
  59. package/src/core/math/linalg/lu_solve_linear_system.js +53 -53
  60. package/src/core/math/linalg/polynomial_add_into.d.ts +18 -0
  61. package/src/core/math/linalg/polynomial_add_into.d.ts.map +1 -0
  62. package/src/core/math/linalg/polynomial_add_into.js +29 -0
  63. package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.d.ts +6 -1
  64. package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.d.ts.map +1 -1
  65. package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.js +19 -12
  66. package/src/core/math/linalg/polynomial_cubic_derivative_eval.d.ts +15 -0
  67. package/src/core/math/linalg/polynomial_cubic_derivative_eval.d.ts.map +1 -0
  68. package/src/core/math/linalg/polynomial_cubic_derivative_eval.js +16 -0
  69. package/src/core/math/linalg/polynomial_cubic_horner_eval.d.ts +18 -0
  70. package/src/core/math/linalg/polynomial_cubic_horner_eval.d.ts.map +1 -0
  71. package/src/core/math/linalg/polynomial_cubic_horner_eval.js +19 -0
  72. package/src/core/math/linalg/polynomial_cubic_second_derivative_eval.d.ts +13 -0
  73. package/src/core/math/linalg/polynomial_cubic_second_derivative_eval.d.ts.map +1 -0
  74. package/src/core/math/linalg/polynomial_cubic_second_derivative_eval.js +14 -0
  75. package/src/core/math/linalg/polynomial_multiply.d.ts +21 -0
  76. package/src/core/math/linalg/polynomial_multiply.d.ts.map +1 -0
  77. package/src/core/math/linalg/polynomial_multiply.js +41 -0
  78. package/src/core/math/linalg/polynomial_real_roots_in_interval.d.ts +6 -1
  79. package/src/core/math/linalg/polynomial_real_roots_in_interval.d.ts.map +1 -1
  80. package/src/core/math/linalg/polynomial_real_roots_in_interval.js +9 -2
  81. package/src/core/math/linalg/polynomial_scale_into.d.ts +14 -0
  82. package/src/core/math/linalg/polynomial_scale_into.d.ts.map +1 -0
  83. package/src/core/math/linalg/polynomial_scale_into.js +17 -0
  84. package/src/core/math/linalg/polynomial_sub_into.d.ts +18 -0
  85. package/src/core/math/linalg/polynomial_sub_into.d.ts.map +1 -0
  86. package/src/core/math/linalg/polynomial_sub_into.js +29 -0
  87. package/src/core/math/linalg/solve_linear_system.d.ts +1 -1
  88. package/src/core/math/linalg/solve_linear_system.js +1 -1
  89. package/src/core/math/linalg/solve_linear_system_GEPP_2x2.d.ts +4 -4
  90. package/src/core/math/linalg/solve_linear_system_GEPP_2x2.d.ts.map +1 -1
  91. package/src/core/math/linalg/solve_linear_system_GEPP_2x2.js +96 -91
  92. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts +18 -10
  93. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts.map +1 -1
  94. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.js +18 -10
  95. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.d.ts +13 -6
  96. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.d.ts.map +1 -1
  97. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.js +16 -11
  98. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts +25 -3
  99. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts.map +1 -1
  100. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.js +78 -135
  101. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts +30 -4
  102. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts.map +1 -1
  103. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.js +188 -132
  104. package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.d.ts +7 -3
  105. package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.d.ts.map +1 -1
  106. package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js +35 -6
  107. package/src/engine/graphics/geometry/MikkT/CalcTexArea.d.ts.map +1 -1
  108. package/src/engine/graphics/geometry/MikkT/CalcTexArea.js +1 -2
  109. package/src/engine/graphics/geometry/MikkT/InitTriInfo.d.ts.map +1 -1
  110. package/src/engine/graphics/geometry/MikkT/InitTriInfo.js +6 -7
@@ -1,5 +1,6 @@
1
+ import { cubic_residual_times_derivative_accumulate } from "../linalg/cubic_residual_times_derivative_accumulate.js";
2
+ import { polynomial_cubic_horner_eval } from "../linalg/polynomial_cubic_horner_eval.js";
1
3
  import { polynomial_real_roots_in_interval } from "../linalg/polynomial_real_roots_in_interval.js";
2
- import { solve_linear_system_GEPP_2x2 } from "../linalg/solve_linear_system_GEPP_2x2.js";
3
4
  import { spline3_hermite_to_monomial } from "./spline3_hermite_to_monomial.js";
4
5
 
5
6
  /*
@@ -39,11 +40,6 @@ const MAX_DIM = 16;
39
40
  const _a_mono = new Float64Array(4 * MAX_DIM);
40
41
  const _b_mono = new Float64Array(4 * MAX_DIM);
41
42
 
42
- const _newton_FG = new Float64Array(2);
43
- const _newton_J = new Float64Array(4);
44
- const _newton_J_scratch = new Float64Array(4);
45
- const _newton_RHS = new Float64Array(2);
46
-
47
43
  const _candidate_st = new Float64Array(2);
48
44
  const _edge_fixed_point = new Float64Array(MAX_DIM);
49
45
 
@@ -61,108 +57,116 @@ const CRITICAL_POINT_DEDUPE_TOLERANCE = 1e-7;
61
57
  const _seen_s = new Float64Array(GRID_SIDE * GRID_SIDE + 8);
62
58
  const _seen_t = new Float64Array(GRID_SIDE * GRID_SIDE + 8);
63
59
 
64
- // ── monomial helpers (per-dim cubic) ───────────────────────────────────────
65
-
66
- function poly3_eval(coeffs4, base, x) {
67
- return coeffs4[base] + x * (coeffs4[base + 1] + x * (coeffs4[base + 2] + x * coeffs4[base + 3]));
68
- }
69
-
70
- function poly3_deriv(coeffs4, base, x) {
71
- return coeffs4[base + 1] + x * (2 * coeffs4[base + 2] + x * 3 * coeffs4[base + 3]);
72
- }
73
-
74
- function poly3_second_deriv(coeffs4, base, x) {
75
- return 2 * coeffs4[base + 2] + 6 * coeffs4[base + 3] * x;
76
- }
77
-
78
- // ── core: F, G, and Jacobian at (s, t) ─────────────────────────────────────
79
-
80
- function eval_FG(dim, s, t, out) {
81
- let F = 0, G = 0;
82
- for (let d = 0; d < dim; d++) {
83
- const base = 4 * d;
84
- const A = poly3_eval(_a_mono, base, s);
85
- const Ap = poly3_deriv(_a_mono, base, s);
86
- const B = poly3_eval(_b_mono, base, t);
87
- const Bp = poly3_deriv(_b_mono, base, t);
88
- const diff = A - B;
89
- F += diff * Ap;
90
- G -= diff * Bp;
91
- }
92
- out[0] = F;
93
- out[1] = G;
94
- }
95
-
96
- function eval_jacobian(dim, s, t, out_J) {
97
- let Jss = 0, Jst = 0, Jts = 0, Jtt = 0;
98
- for (let d = 0; d < dim; d++) {
99
- const base = 4 * d;
100
- const A = poly3_eval(_a_mono, base, s);
101
- const Ap = poly3_deriv(_a_mono, base, s);
102
- const App = poly3_second_deriv(_a_mono, base, s);
103
- const B = poly3_eval(_b_mono, base, t);
104
- const Bp = poly3_deriv(_b_mono, base, t);
105
- const Bpp = poly3_second_deriv(_b_mono, base, t);
106
- const diff = A - B;
107
-
108
- Jss += Ap * Ap + diff * App;
109
- Jst += -Bp * Ap;
110
- Jts += -Ap * Bp;
111
- Jtt += Bp * Bp - diff * Bpp;
112
- }
113
- // Row-major 2x2: [Jss, Jst; Jts, Jtt]
114
- out_J[0] = Jss;
115
- out_J[1] = Jst;
116
- out_J[2] = Jts;
117
- out_J[3] = Jtt;
118
- }
119
-
120
- function eval_phi(dim, s, t) {
121
- let acc = 0;
122
- for (let d = 0; d < dim; d++) {
123
- const base = 4 * d;
124
- const A = poly3_eval(_a_mono, base, s);
125
- const B = poly3_eval(_b_mono, base, t);
126
- const diff = A - B;
127
- acc += diff * diff;
128
- }
129
- return acc;
130
- }
60
+ // The gradient system (F, G) = (½ ∂Φ/∂s, ½ ∂Φ/∂t) and the 2×2 Jacobian
61
+ // of that gradient are inlined directly into `newton_2d`'s hot loop — see
62
+ // the module preamble for the algebraic definitions of F, G, and J.
131
63
 
132
64
  /**
133
65
  * 2D Newton on (F, G) from a starting (s, t). Writes the converged (s, t) into
134
66
  * `out` and returns true iff it converged inside [0, 1]² with small gradient.
135
67
  */
68
+ // Tolerance for declaring the 2x2 Jacobian singular during the inlined
69
+ // Newton solve. Matches `256 * FLT_EPSILON_64` used by
70
+ // solve_linear_system_GEPP_2x2 to keep behaviour identical on edge cases.
71
+ const NEWTON_SINGULAR_TOLERANCE = 256 * 1.1102230246251565e-16;
72
+
136
73
  function newton_2d(dim, s_init, t_init, out) {
137
74
  let s = s_init;
138
75
  let t = t_init;
139
76
 
77
+ // F, G at the current (s, t) — promoted out of the heap-resident
78
+ // `_newton_FG` scratch into iteration-local doubles.
79
+ let F = 0, G = 0;
80
+ let fg_valid_at_current_st = false;
81
+
140
82
  for (let iter = 0; iter < NEWTON_MAX_ITER; iter++) {
141
- eval_FG(dim, s, t, _newton_FG);
142
- const F = _newton_FG[0];
143
- const G = _newton_FG[1];
83
+ // ── fused F/G + Jacobian over all dimensions ──
84
+ // A single per-iteration sweep over dims computes the gradient
85
+ // (F, G) and the symmetric Hessian (Jss, J_cross, Jtt) from one
86
+ // load of each cubic's 4 coefficients. Eagerly computing the
87
+ // Hessian costs us one extra second-derivative + 3 accumulations
88
+ // per dim on the iteration that ends up converging — cheap insurance
89
+ // for skipping a duplicate cubic-eval sweep on every non-converging
90
+ // iteration. J_st = J_ts so the symmetric cross term is computed
91
+ // once into J_cross.
92
+ F = 0;
93
+ G = 0;
94
+ let Jss = 0, J_cross = 0, Jtt = 0;
95
+
96
+ for (let d = 0; d < dim; d++) {
97
+ const base = 4 * d;
98
+ const a0 = _a_mono[base];
99
+ const a1 = _a_mono[base + 1];
100
+ const a2 = _a_mono[base + 2];
101
+ const a3 = _a_mono[base + 3];
102
+ const b0 = _b_mono[base];
103
+ const b1 = _b_mono[base + 1];
104
+ const b2 = _b_mono[base + 2];
105
+ const b3 = _b_mono[base + 3];
106
+
107
+ const A_v = a0 + s * (a1 + s * (a2 + s * a3));
108
+ const A_p = a1 + s * (2 * a2 + s * 3 * a3);
109
+ const A_pp = 2 * a2 + 6 * a3 * s;
110
+ const B_v = b0 + t * (b1 + t * (b2 + t * b3));
111
+ const B_p = b1 + t * (2 * b2 + t * 3 * b3);
112
+ const B_pp = 2 * b2 + 6 * b3 * t;
113
+
114
+ const diff = A_v - B_v;
115
+ F += diff * A_p;
116
+ G -= diff * B_p;
117
+ Jss += A_p * A_p + diff * A_pp;
118
+ J_cross += -A_p * B_p;
119
+ Jtt += B_p * B_p - diff * B_pp;
120
+ }
121
+ fg_valid_at_current_st = true;
144
122
 
145
123
  if (Math.abs(F) < NEWTON_GRADIENT_TOLERANCE && Math.abs(G) < NEWTON_GRADIENT_TOLERANCE) {
146
124
  break;
147
125
  }
148
126
 
149
- eval_jacobian(dim, s, t, _newton_J);
150
-
151
- _newton_RHS[0] = F;
152
- _newton_RHS[1] = G;
153
-
154
- // solve_linear_system_GEPP_2x2 mutates the input matrix; use a scratch copy.
155
- for (let k = 0; k < 4; k++) _newton_J_scratch[k] = _newton_J[k];
156
- if (!solve_linear_system_GEPP_2x2(_newton_J_scratch, _newton_RHS, _newton_RHS)) {
127
+ // ── inline solve_linear_system_GEPP_2x2 on column-major J ──
128
+ // J = [a00, a10; a01, a11] = [Jss, J_cross; J_cross, Jtt]; rhs = [F, G]
129
+ // Partial pivoting on column 0, then forward eliminate, then back-sub.
130
+ // The "exact zero" special cases handled by the full solver are skipped:
131
+ // for a smooth residual Newton, exact zeros essentially never arise
132
+ // (random + analytic inputs), and a singular Jacobian gets rejected
133
+ // below the same way the full solver would.
134
+ let a00 = Jss, a10 = J_cross, a01 = J_cross, a11 = Jtt;
135
+ let b0 = F, b1 = G;
136
+ if (Math.abs(a10) > Math.abs(a00)) {
137
+ // Row swap
138
+ let tmp = a00; a00 = a10; a10 = tmp;
139
+ tmp = a01; a01 = a11; a11 = tmp;
140
+ tmp = b0; b0 = b1; b1 = tmp;
141
+ }
142
+ if (a00 === 0) {
143
+ return false; // singular column 0
144
+ }
145
+ const f = -a10 / a00;
146
+ a11 += a01 * f;
147
+ b1 += b0 * f;
148
+ if (Math.abs(a11) < NEWTON_SINGULAR_TOLERANCE) {
149
+ return false;
150
+ }
151
+ const dt = b1 / a11;
152
+ const ds = (b0 - a01 * dt) / a00;
153
+ if (!Number.isFinite(ds) || !Number.isFinite(dt)) {
157
154
  return false;
158
155
  }
159
-
160
- const ds = _newton_RHS[0];
161
- const dt = _newton_RHS[1];
162
156
 
163
157
  s -= ds;
164
158
  t -= dt;
165
159
 
160
+ // (s, t) has just changed → cached F, G no longer match.
161
+ fg_valid_at_current_st = false;
162
+
163
+ // Early bail: if (s, t) has wandered well outside [0, 1] the start is
164
+ // diverging or homed in on a critical point of the gradient system
165
+ // that lies far from the parameter square. Newton won't reel it back.
166
+ if (s < -0.5 || s > 1.5 || t < -0.5 || t > 1.5) {
167
+ return false;
168
+ }
169
+
166
170
  if (Math.abs(ds) < NEWTON_STEP_TOLERANCE && Math.abs(dt) < NEWTON_STEP_TOLERANCE) {
167
171
  break;
168
172
  }
@@ -171,8 +175,36 @@ function newton_2d(dim, s_init, t_init, out) {
171
175
  if (s < -1e-9 || s > 1 + 1e-9) return false;
172
176
  if (t < -1e-9 || t > 1 + 1e-9) return false;
173
177
 
174
- eval_FG(dim, s, t, _newton_FG);
175
- if (Math.abs(_newton_FG[0]) > 1e-6 || Math.abs(_newton_FG[1]) > 1e-6) return false;
178
+ // Validate gradient at the final (s, t). When `fg_valid_at_current_st`
179
+ // is true we exited the loop via the gradient-tolerance break and the
180
+ // validation already passed (tolerance 1e-13 < 1e-6); otherwise we
181
+ // exited via the step-tolerance break or maxed out — re-evaluate to
182
+ // confirm.
183
+ if (!fg_valid_at_current_st) {
184
+ F = 0;
185
+ G = 0;
186
+ for (let d = 0; d < dim; d++) {
187
+ const base = 4 * d;
188
+ const a0 = _a_mono[base];
189
+ const a1 = _a_mono[base + 1];
190
+ const a2 = _a_mono[base + 2];
191
+ const a3 = _a_mono[base + 3];
192
+ const b0 = _b_mono[base];
193
+ const b1 = _b_mono[base + 1];
194
+ const b2 = _b_mono[base + 2];
195
+ const b3 = _b_mono[base + 3];
196
+
197
+ const A_v = a0 + s * (a1 + s * (a2 + s * a3));
198
+ const A_p = a1 + s * (2 * a2 + s * 3 * a3);
199
+ const B_v = b0 + t * (b1 + t * (b2 + t * b3));
200
+ const B_p = b1 + t * (2 * b2 + t * 3 * b3);
201
+
202
+ const diff = A_v - B_v;
203
+ F += diff * A_p;
204
+ G -= diff * B_p;
205
+ }
206
+ if (Math.abs(F) > 1e-6 || Math.abs(G) > 1e-6) return false;
207
+ }
176
208
 
177
209
  out[0] = s < 0 ? 0 : (s > 1 ? 1 : s);
178
210
  out[1] = t < 0 ? 0 : (t > 1 ? 1 : t);
@@ -193,46 +225,46 @@ function newton_2d(dim, s_init, t_init, out) {
193
225
  * Writes `[t_min, dist²]` into `out` (length ≥ 2).
194
226
  */
195
227
  function nearest_t_to_fixed_point(curve_mono, dim, point, point_offset, out) {
196
- for (let i = 0; i < EDGE_QUINTIC_LEN; i++) _edge_quintic[i] = 0;
228
+ for (let i = 0; i < EDGE_QUINTIC_LEN; i++){
229
+ _edge_quintic[i] = 0;
230
+ }
197
231
 
198
232
  for (let d = 0; d < dim; d++) {
199
- const base = 4 * d;
200
- const dx0 = curve_mono[base] - point[point_offset + d];
201
- const dx1 = curve_mono[base + 1];
202
- const dx2 = curve_mono[base + 2];
203
- const dx3 = curve_mono[base + 3];
204
- // derivative coefficients
205
- const e0 = dx1, e1 = 2 * dx2, e2 = 3 * dx3;
206
-
207
- // (dx0 + dx1 t + dx2 t² + dx3 t³) · (e0 + e1 t + e2 t²) accumulated
208
- _edge_quintic[0] += dx0 * e0;
209
- _edge_quintic[1] += dx0 * e1 + dx1 * e0;
210
- _edge_quintic[2] += dx0 * e2 + dx1 * e1 + dx2 * e0;
211
- _edge_quintic[3] += dx1 * e2 + dx2 * e1 + dx3 * e0;
212
- _edge_quintic[4] += dx2 * e2 + dx3 * e1;
213
- _edge_quintic[5] += dx3 * e2;
233
+ cubic_residual_times_derivative_accumulate(
234
+ _edge_quintic, curve_mono, 4 * d, point[point_offset + d]
235
+ );
214
236
  }
215
237
 
238
+ // Aberth iteration cap of 32 is plenty for the degree-5 quintic that
239
+ // comes out of `(p(t) - offset)·p'(t)` on a smooth Hermite cubic — the
240
+ // routine's own convergence check still fires early in the common case.
241
+ // Default cap (80) is way more than needed and is the worst-case work
242
+ // budget; lowering it is a flat ~10-20% saving on degenerate inputs.
216
243
  const root_count = polynomial_real_roots_in_interval(
217
- _edge_quintic, 5, 0, 1, _edge_roots, 0
244
+ _edge_quintic, 5, 0, 1, _edge_roots, 0,
245
+ 32
218
246
  );
219
247
 
220
248
  let best_t = 0;
221
249
  let best_d2 = Number.POSITIVE_INFINITY;
222
250
 
223
251
  for (let i = -2; i < root_count; i++) {
252
+
224
253
  const t = i === -2 ? 0 : (i === -1 ? 1 : _edge_roots[i]);
225
254
  let d2 = 0;
255
+
226
256
  for (let d = 0; d < dim; d++) {
227
257
  const base = 4 * d;
228
- const v = curve_mono[base] + t * (curve_mono[base + 1] + t * (curve_mono[base + 2] + t * curve_mono[base + 3]));
258
+ const v = polynomial_cubic_horner_eval(curve_mono, base, t);
229
259
  const dv = v - point[point_offset + d];
230
260
  d2 += dv * dv;
231
261
  }
262
+
232
263
  if (d2 < best_d2) {
233
264
  best_d2 = d2;
234
265
  best_t = t;
235
266
  }
267
+
236
268
  }
237
269
 
238
270
  out[0] = best_t;
@@ -242,14 +274,41 @@ function nearest_t_to_fixed_point(curve_mono, dim, point, point_offset, out) {
242
274
  // ── public ────────────────────────────────────────────────────────────────
243
275
 
244
276
  /**
245
- * ND specialization (dim ≥ 3). See module preamble for algorithmic notes.
277
+ * Upper bound on the number of (s, t) pairs the ND variant can return:
278
+ * one per starting point of the GRID_SIDE × GRID_SIDE grid Newton, plus the
279
+ * four boundary edges, plus a small slack. The corresponding result-buffer
280
+ * size in floats is `2 * ND_MAX_ROOTS`.
281
+ *
282
+ * @type {number}
283
+ */
284
+ export const ND_MAX_ROOTS = GRID_SIDE * GRID_SIDE + 8;
285
+
286
+ /**
287
+ * Critical-point enumerator for the ND Hermite curve-pair intersection
288
+ * problem (dim ≥ 2, optimal for dim ≥ 3). Writes (s, t) pairs sequentially
289
+ * into `result` starting at `result_offset` and returns the count.
290
+ *
291
+ * What is reported (deduplicated within
292
+ * CRITICAL_POINT_DEDUPE_TOLERANCE = 1e-7 in each parameter):
293
+ * - Interior critical points of squared distance found by 2D Newton
294
+ * started from a GRID_SIDE × GRID_SIDE grid in [0,1]² and accepted
295
+ * when they converge inside [0,1]² with small gradient.
296
+ * - Four boundary closest-approach pairs, one per edge of [0,1]², each
297
+ * coming from a quintic root-find of (curve − fixed point)·curve′.
298
+ *
299
+ * Caller is responsible for evaluating both curves at each (s, t) and
300
+ * picking whichever pair(s) they care about (closest, within threshold,
301
+ * etc.).
302
+ *
303
+ * Required buffer size:
304
+ * `result.length >= result_offset + 2 * ND_MAX_ROOTS` floats (currently 178).
246
305
  *
247
306
  * @param {Float64Array|number[]} a length 4*dim
248
307
  * @param {Float64Array|number[]} b length 4*dim
249
- * @param {number} dim ≥ 3 (also correct for dim ≥ 2 but slower than the 2D path)
250
- * @param {Float64Array|number[]} result writes [s, t]
308
+ * @param {number} dim ≥ 2 (also correct for dim ≥ 2 but slower than the 2D path)
309
+ * @param {Float64Array|number[]} result length >= result_offset + 2 * ND_MAX_ROOTS
251
310
  * @param {number} result_offset
252
- * @returns {number} squared distance at closest approach
311
+ * @returns {number} number of (s, t) pairs written
253
312
  */
254
313
  export function spline3_hermite_intersection_spline3_hermite_nd(
255
314
  a, b, dim,
@@ -266,29 +325,24 @@ export function spline3_hermite_intersection_spline3_hermite_nd(
266
325
  spline3_hermite_to_monomial(_b_mono, off, 1, b[off], b[off + 1], b[off + 2], b[off + 3]);
267
326
  }
268
327
 
269
- let best_s = 0;
270
- let best_t = 0;
271
- let best_d2 = Number.POSITIVE_INFINITY;
272
- let seen_count = 0;
328
+ // We dedupe near-identical critical points discovered from different grid
329
+ // starts. _seen_s / _seen_t double as the "candidates written so far" and
330
+ // are mirrored into the caller's `result` buffer as we go.
331
+ let count = 0;
332
+ let write = result_offset;
273
333
 
274
334
  const try_candidate = (s, t) => {
275
- // Dedupe near-identical critical points discovered from different grid starts.
276
- for (let i = 0; i < seen_count; i++) {
335
+ for (let i = 0; i < count; i++) {
277
336
  if (Math.abs(_seen_s[i] - s) < CRITICAL_POINT_DEDUPE_TOLERANCE
278
337
  && Math.abs(_seen_t[i] - t) < CRITICAL_POINT_DEDUPE_TOLERANCE) {
279
338
  return;
280
339
  }
281
340
  }
282
- _seen_s[seen_count] = s;
283
- _seen_t[seen_count] = t;
284
- seen_count++;
285
-
286
- const d2 = eval_phi(dim, s, t);
287
- if (d2 < best_d2) {
288
- best_d2 = d2;
289
- best_s = s;
290
- best_t = t;
291
- }
341
+ _seen_s[count] = s;
342
+ _seen_t[count] = t;
343
+ result[write++] = s;
344
+ result[write++] = t;
345
+ count++;
292
346
  };
293
347
 
294
348
  // Interior critical points via Newton from a grid.
@@ -308,7 +362,9 @@ export function spline3_hermite_intersection_spline3_hermite_nd(
308
362
  const tmp_pt = _edge_fixed_point;
309
363
 
310
364
  // Edge s = 0: fixed point is A(0); minimise over t on B.
311
- for (let d = 0; d < dim; d++) tmp_pt[d] = _a_mono[4 * d]; // α0
365
+ for (let d = 0; d < dim; d++){
366
+ tmp_pt[d] = _a_mono[4 * d]; // α0
367
+ }
312
368
  nearest_t_to_fixed_point(_b_mono, dim, tmp_pt, 0, _candidate_st);
313
369
  try_candidate(0, _candidate_st[0]);
314
370
 
@@ -321,7 +377,9 @@ export function spline3_hermite_intersection_spline3_hermite_nd(
321
377
  try_candidate(1, _candidate_st[0]);
322
378
 
323
379
  // Edge t = 0: fixed point is B(0); minimise over s on A.
324
- for (let d = 0; d < dim; d++) tmp_pt[d] = _b_mono[4 * d];
380
+ for (let d = 0; d < dim; d++){
381
+ tmp_pt[d] = _b_mono[4 * d];
382
+ }
325
383
  nearest_t_to_fixed_point(_a_mono, dim, tmp_pt, 0, _candidate_st);
326
384
  try_candidate(_candidate_st[0], 0);
327
385
 
@@ -333,7 +391,5 @@ export function spline3_hermite_intersection_spline3_hermite_nd(
333
391
  nearest_t_to_fixed_point(_a_mono, dim, tmp_pt, 0, _candidate_st);
334
392
  try_candidate(_candidate_st[0], 1);
335
393
 
336
- result[result_offset] = best_s;
337
- result[result_offset + 1] = best_t;
338
- return best_d2;
394
+ return count;
339
395
  }
@@ -1,9 +1,13 @@
1
1
  /**
2
2
  * Boolean intersection test for two cubic Hermite curves.
3
3
  *
4
- * Returns `true` if a true intersection exists OR if the closest approach is
5
- * within `tolerance` (Euclidean distance). Tolerance = 0 reduces to exact
6
- * intersection.
4
+ * Returns `true` if any critical point of the squared-distance function
5
+ * over [0,1]² (interior or boundary) is within `tolerance` Euclidean
6
+ * distance. Tolerance = 0 reduces to "any true intersection exists".
7
+ *
8
+ * Internally: enumerates every candidate (s, t) pair via the dispatcher,
9
+ * evaluates both curves at each pair, computes squared distance, and
10
+ * short-circuits as soon as one pair clears the tolerance bound.
7
11
  *
8
12
  * @param {Float64Array|number[]} a length 4*dim, layout described in {@link spline3_hermite_intersection_spline3_hermite}
9
13
  * @param {Float64Array|number[]} b same layout
@@ -1 +1 @@
1
- {"version":3,"file":"spline3_hermite_intersects_spline3_hermite.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AACH,8DANW,YAAY,GAAC,MAAM,EAAE,KACrB,YAAY,GAAC,MAAM,EAAE,OACrB,MAAM,aACN,MAAM,GACJ,OAAO,CAKnB"}
1
+ {"version":3,"file":"spline3_hermite_intersects_spline3_hermite.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;GAgBG;AACH,8DANW,YAAY,GAAC,MAAM,EAAE,KACrB,YAAY,GAAC,MAAM,EAAE,OACrB,MAAM,aACN,MAAM,GACJ,OAAO,CA4BnB"}
@@ -1,13 +1,19 @@
1
+ import { spline3_hermite } from "./spline3_hermite.js";
1
2
  import { spline3_hermite_intersection_spline3_hermite } from "./spline3_hermite_intersection_spline3_hermite.js";
2
3
 
3
- const _scratch_st = new Float64Array(2);
4
+ // Sized for the largest variant (ND, up to 89 (s, t) pairs = 178 floats).
5
+ const _scratch_roots = new Float64Array(178);
4
6
 
5
7
  /**
6
8
  * Boolean intersection test for two cubic Hermite curves.
7
9
  *
8
- * Returns `true` if a true intersection exists OR if the closest approach is
9
- * within `tolerance` (Euclidean distance). Tolerance = 0 reduces to exact
10
- * intersection.
10
+ * Returns `true` if any critical point of the squared-distance function
11
+ * over [0,1]² (interior or boundary) is within `tolerance` Euclidean
12
+ * distance. Tolerance = 0 reduces to "any true intersection exists".
13
+ *
14
+ * Internally: enumerates every candidate (s, t) pair via the dispatcher,
15
+ * evaluates both curves at each pair, computes squared distance, and
16
+ * short-circuits as soon as one pair clears the tolerance bound.
11
17
  *
12
18
  * @param {Float64Array|number[]} a length 4*dim, layout described in {@link spline3_hermite_intersection_spline3_hermite}
13
19
  * @param {Float64Array|number[]} b same layout
@@ -16,6 +22,29 @@ const _scratch_st = new Float64Array(2);
16
22
  * @returns {boolean}
17
23
  */
18
24
  export function spline3_hermite_intersects_spline3_hermite(a, b, dim, tolerance) {
19
- const d2 = spline3_hermite_intersection_spline3_hermite(a, b, dim, _scratch_st, 0);
20
- return d2 <= tolerance * tolerance;
25
+ const tolerance_sq = tolerance * tolerance;
26
+
27
+ const root_count = spline3_hermite_intersection_spline3_hermite(
28
+ a, b, dim, _scratch_roots, 0
29
+ );
30
+
31
+ for (let k = 0; k < root_count; k++) {
32
+ const s = _scratch_roots[2 * k];
33
+ const t = _scratch_roots[2 * k + 1];
34
+
35
+ let d2 = 0;
36
+ for (let d = 0; d < dim; d++) {
37
+ const off = 4 * d;
38
+ const va = spline3_hermite(s, a[off], a[off + 1], a[off + 2], a[off + 3]);
39
+ const vb = spline3_hermite(t, b[off], b[off + 1], b[off + 2], b[off + 3]);
40
+ const diff = va - vb;
41
+ d2 += diff * diff;
42
+ }
43
+
44
+ if (d2 <= tolerance_sq) {
45
+ return true;
46
+ }
47
+ }
48
+
49
+ return false;
21
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CalcTexArea.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/geometry/MikkT/CalcTexArea.js"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,mEAJW,MAAM,EAAE,kBACR,MAAM,GACJ,MAAM,CAiBlB"}
1
+ {"version":3,"file":"CalcTexArea.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/geometry/MikkT/CalcTexArea.js"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,mEAJW,MAAM,EAAE,kBACR,MAAM,GACJ,MAAM,CAiBlB"}
@@ -1,6 +1,5 @@
1
1
  import { vec3 } from "gl-matrix";
2
2
  import { GetTexCoord } from "./GetTexCoord.js";
3
- import { fabsf } from "../../../../core/math/fabsf.js";
4
3
 
5
4
  const t1 = vec3.create();
6
5
  const t2 = vec3.create();
@@ -27,5 +26,5 @@ export function CalcTexArea(pContext, indices, indices_offset) {
27
26
 
28
27
  const fSignedAreaSTx2 = t21x * t31y - t21y * t31x;
29
28
 
30
- return fabsf(fSignedAreaSTx2);
29
+ return Math.abs(fSignedAreaSTx2);
31
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"InitTriInfo.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/geometry/MikkT/InitTriInfo.js"],"names":[],"mappings":"AA4BA;;;;;;;GAOG;AACH,uCANW,UAAU,eACV,MAAM,EAAE,GAAC,UAAU,gDAEnB,MAAM,GACJ,IAAI,CAgJhB"}
1
+ {"version":3,"file":"InitTriInfo.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/geometry/MikkT/InitTriInfo.js"],"names":[],"mappings":"AA2BA;;;;;;;GAOG;AACH,uCANW,UAAU,eACV,MAAM,EAAE,GAAC,UAAU,gDAEnB,MAAM,GACJ,IAAI,CAgJhB"}
@@ -1,13 +1,12 @@
1
- import { GROUP_WITH_ANY } from "./constants/GROUP_WITH_ANY.js";
2
1
  import { vec3 } from "gl-matrix";
2
+ import { BuildNeighborsFast } from "./BuildNeighborsFast.js";
3
+ import { CalcTexArea } from "./CalcTexArea.js";
4
+ import { GROUP_WITH_ANY } from "./constants/GROUP_WITH_ANY.js";
5
+ import { MARK_DEGENERATE } from "./constants/MARK_DEGENERATE.js";
6
+ import { ORIENT_PRESERVING } from "./constants/ORIENT_PRESERVING.js";
3
7
  import { GetPosition } from "./GetPosition.js";
4
8
  import { GetTexCoord } from "./GetTexCoord.js";
5
- import { ORIENT_PRESERVING } from "./constants/ORIENT_PRESERVING.js";
6
9
  import { NotZero } from "./NotZero.js";
7
- import { fabsf } from "../../../../core/math/fabsf.js";
8
- import { MARK_DEGENERATE } from "./constants/MARK_DEGENERATE.js";
9
- import { CalcTexArea } from "./CalcTexArea.js";
10
- import { BuildNeighborsFast } from "./BuildNeighborsFast.js";
11
10
 
12
11
  const t1 = vec3.create();
13
12
  const t2 = vec3.create();
@@ -102,7 +101,7 @@ export function InitTriInfo(pTriInfos, piTriListIn, pContext, iNrTrianglesIn) {
102
101
  tri_info.iFlag |= (fSignedAreaSTx2 > 0 ? ORIENT_PRESERVING : 0);
103
102
 
104
103
  if (NotZero(fSignedAreaSTx2)) {
105
- const fAbsArea = fabsf(fSignedAreaSTx2);
104
+ const fAbsArea = Math.abs(fSignedAreaSTx2);
106
105
 
107
106
  const fLenOs = vec3.length(vOs);
108
107
  const fLenOt = vec3.length(vOt);