@woosh/meep-engine 2.146.0 → 2.148.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 (105) hide show
  1. package/package.json +1 -1
  2. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts +4 -4
  3. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts.map +1 -1
  4. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.js +48 -52
  5. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts +23 -21
  6. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts.map +1 -1
  7. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.js +41 -406
  8. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts +5 -4
  9. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts.map +1 -1
  10. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.js +400 -395
  11. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +0 -11
  12. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  13. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +8 -6
  14. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  15. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +552 -551
  16. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +8 -3
  17. package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
  18. package/src/engine/control/first-person/abilities/LedgeGrab.js +213 -199
  19. package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
  20. package/src/engine/control/first-person/abilities/Mantle.js +195 -188
  21. package/src/engine/control/first-person/abilities/WallRun.d.ts.map +1 -1
  22. package/src/engine/control/first-person/abilities/WallRun.js +183 -175
  23. package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts +9 -0
  24. package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts.map +1 -1
  25. package/src/engine/control/first-person/sensors/FirstPersonSensors.js +87 -77
  26. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts +8 -0
  27. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts.map +1 -1
  28. package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.js +229 -196
  29. package/src/engine/ecs/EntityManager.d.ts +34 -11
  30. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  31. package/src/engine/ecs/EntityManager.js +71 -42
  32. package/src/engine/interpolation/BinaryInterpolationAdapter.d.ts.map +1 -0
  33. package/src/engine/interpolation/Interpoland.d.ts +48 -0
  34. package/src/engine/interpolation/Interpoland.d.ts.map +1 -0
  35. package/src/engine/interpolation/Interpoland.js +49 -0
  36. package/src/engine/interpolation/Interpolated.d.ts +101 -0
  37. package/src/engine/interpolation/Interpolated.d.ts.map +1 -0
  38. package/src/engine/interpolation/Interpolated.js +149 -0
  39. package/src/engine/{network/sim → interpolation}/InterpolationLog.d.ts +1 -1
  40. package/src/engine/interpolation/InterpolationLog.d.ts.map +1 -0
  41. package/src/engine/{network/sim → interpolation}/InterpolationLog.js +2 -2
  42. package/src/engine/interpolation/InterpolationSystem.d.ts +116 -0
  43. package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -0
  44. package/src/engine/interpolation/InterpolationSystem.js +233 -0
  45. package/src/engine/interpolation/PoseInterpolationAdapter.d.ts +17 -0
  46. package/src/engine/interpolation/PoseInterpolationAdapter.d.ts.map +1 -0
  47. package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -0
  48. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +35 -0
  49. package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -0
  50. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -0
  51. package/src/engine/interpolation/pose_interpoland.d.ts +18 -0
  52. package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -0
  53. package/src/engine/interpolation/pose_interpoland.js +27 -0
  54. package/src/engine/network/NetworkSession.d.ts +2 -2
  55. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  56. package/src/engine/network/NetworkSession.js +2 -2
  57. package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts +1 -1
  58. package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts.map +1 -1
  59. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +1 -1
  60. package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts +1 -1
  61. package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts.map +1 -1
  62. package/src/engine/network/adapters/TransformInterpolationAdapter.js +1 -1
  63. package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts +1 -1
  64. package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts.map +1 -1
  65. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +1 -1
  66. package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -0
  67. package/src/engine/physics/PLAN.md +10 -9
  68. package/src/engine/physics/body/SolverBodyState.d.ts +142 -0
  69. package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -0
  70. package/src/engine/physics/body/SolverBodyState.js +251 -0
  71. package/src/engine/physics/broadphase/generate_pairs.d.ts +2 -1
  72. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
  73. package/src/engine/physics/broadphase/generate_pairs.js +5 -3
  74. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  75. package/src/engine/physics/constraint/solve_constraints.js +691 -673
  76. package/src/engine/physics/ecs/PhysicsSystem.d.ts +82 -15
  77. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  78. package/src/engine/physics/ecs/PhysicsSystem.js +387 -87
  79. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +23 -0
  80. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  81. package/src/engine/physics/inertia/world_inverse_inertia.js +116 -77
  82. package/src/engine/physics/integration/integrate_position.d.ts +11 -1
  83. package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
  84. package/src/engine/physics/integration/integrate_position.js +97 -79
  85. package/src/engine/physics/integration/integrate_velocity.d.ts +12 -3
  86. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
  87. package/src/engine/physics/integration/integrate_velocity.js +201 -160
  88. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  89. package/src/engine/physics/narrowphase/box_box_manifold.js +750 -665
  90. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
  91. package/src/engine/physics/narrowphase/box_triangle_contact.js +19 -46
  92. package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts +16 -0
  93. package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts.map +1 -0
  94. package/src/engine/physics/narrowphase/clip_against_axis_uv.js +49 -0
  95. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  96. package/src/engine/physics/narrowphase/narrowphase_step.js +52 -4
  97. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  98. package/src/engine/physics/queries/raycast.js +7 -4
  99. package/src/engine/physics/solver/solve_contacts.d.ts +2 -2
  100. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  101. package/src/engine/physics/solver/solve_contacts.js +1341 -1173
  102. package/src/engine/network/sim/BinaryInterpolationAdapter.d.ts.map +0 -1
  103. package/src/engine/network/sim/InterpolationLog.d.ts.map +0 -1
  104. /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.d.ts +0 -0
  105. /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.js +0 -0
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Pure JavaScript game engine. Fully featured and production ready.",
7
7
  "type": "module",
8
8
  "author": "Alexander Goldring",
9
- "version": "2.146.0",
9
+ "version": "2.148.0",
10
10
  "main": "build/meep.module.js",
11
11
  "module": "build/meep.module.js",
12
12
  "exports": {
@@ -13,10 +13,10 @@
13
13
  * of candidate critical points (see the variant docs for what each reports
14
14
  * and how many entries the result buffer needs):
15
15
  * - 1D: range overlap or extrema-based closest pair → 1 pair.
16
- * - 2D: (3,3)/(3,3) Bezout interior intersections + boundary minima → up
17
- * to 13 pairs (~26 floats).
18
- * - ND (≥ 3): grid-Newton interior critical points + boundary minima → up
19
- * to 89 pairs (~178 floats).
16
+ * - ND (≥ 2): grid-Newton interior critical points + boundary minima → up
17
+ * to 89 pairs (~178 floats). dim === 2 uses this path too; the former
18
+ * bespoke 2D Bezout specialization was removed because it missed interior
19
+ * non-intersection closest approaches.
20
20
  *
21
21
  * Required buffer size: caller must reserve enough space for the worst
22
22
  * case of the dim it intends to call with. Reserving ≥ 178 floats past
@@ -1 +1 @@
1
- {"version":3,"file":"spline3_hermite_intersection_spline3_hermite.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_hermite_intersection_spline3_hermite.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,gEAPW,YAAY,GAAC,MAAM,EAAE,KACrB,YAAY,GAAC,MAAM,EAAE,OACrB,MAAM,UACN,YAAY,GAAC,MAAM,EAAE,iBACrB,MAAM,GACJ,MAAM,CAiBlB"}
1
+ {"version":3,"file":"spline3_hermite_intersection_spline3_hermite.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_hermite_intersection_spline3_hermite.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,gEAPW,YAAY,GAAC,MAAM,EAAE,KACrB,YAAY,GAAC,MAAM,EAAE,OACrB,MAAM,UACN,YAAY,GAAC,MAAM,EAAE,iBACrB,MAAM,GACJ,MAAM,CAclB"}
@@ -1,52 +1,48 @@
1
- import { assert } from "../../assert.js";
2
- import { spline3_hermite_intersection_spline3_hermite_1d } from "./spline3_hermite_intersection_spline3_hermite_1d.js";
3
- import { spline3_hermite_intersection_spline3_hermite_2d } from "./spline3_hermite_intersection_spline3_hermite_2d.js";
4
- import { spline3_hermite_intersection_spline3_hermite_nd } from "./spline3_hermite_intersection_spline3_hermite_nd.js";
5
-
6
- /**
7
- * Critical points of squared distance between two cubic Hermite curves over
8
- * the [0,1]² parameter square. Writes (s, t) pairs sequentially into
9
- * `result` starting at `result_offset` and returns the count of pairs
10
- * written. Caller is responsible for evaluating both curves at each pair
11
- * and computing distance (e.g. to pick the nearest root).
12
- *
13
- * Coefficient layout for `a` and `b`: per-dimension grouped quads,
14
- * `[p0_0, p1_0, m0_0, m1_0, p0_1, p1_1, m0_1, m1_1, ..., m1_{dim-1}]`,
15
- * length `4 * dim`. dim ≥ 1.
16
- *
17
- * Internally dispatched by dimension; each path enumerates different sets
18
- * of candidate critical points (see the variant docs for what each reports
19
- * and how many entries the result buffer needs):
20
- * - 1D: range overlap or extrema-based closest pair1 pair.
21
- * - 2D: (3,3)/(3,3) Bezout interior intersections + boundary minima up
22
- * to 13 pairs (~26 floats).
23
- * - ND (≥ 3): grid-Newton interior critical points + boundary minima → up
24
- * to 89 pairs (~178 floats).
25
- *
26
- * Required buffer size: caller must reserve enough space for the worst
27
- * case of the dim it intends to call with. Reserving ≥ 178 floats past
28
- * `result_offset` covers all dims.
29
- *
30
- * @param {Float64Array|number[]} a length 4*dim
31
- * @param {Float64Array|number[]} b length 4*dim
32
- * @param {number} dim
33
- * @param {Float64Array|number[]} result
34
- * @param {number} result_offset
35
- * @returns {number} number of (s, t) pairs written
36
- */
37
- export function spline3_hermite_intersection_spline3_hermite(
38
- a, b, dim,
39
- result, result_offset
40
- ) {
41
- assert.greaterThanOrEqual(dim, 1, 'dim');
42
-
43
- if (dim === 1) {
44
- return spline3_hermite_intersection_spline3_hermite_1d(a, b, result, result_offset);
45
- }
46
-
47
- if (dim === 2) {
48
- return spline3_hermite_intersection_spline3_hermite_2d(a, b, result, result_offset);
49
- }
50
-
51
- return spline3_hermite_intersection_spline3_hermite_nd(a, b, dim, result, result_offset);
52
- }
1
+ import { assert } from "../../assert.js";
2
+ import { spline3_hermite_intersection_spline3_hermite_1d } from "./spline3_hermite_intersection_spline3_hermite_1d.js";
3
+ import { spline3_hermite_intersection_spline3_hermite_nd } from "./spline3_hermite_intersection_spline3_hermite_nd.js";
4
+
5
+ /**
6
+ * Critical points of squared distance between two cubic Hermite curves over
7
+ * the [0,1]² parameter square. Writes (s, t) pairs sequentially into
8
+ * `result` starting at `result_offset` and returns the count of pairs
9
+ * written. Caller is responsible for evaluating both curves at each pair
10
+ * and computing distance (e.g. to pick the nearest root).
11
+ *
12
+ * Coefficient layout for `a` and `b`: per-dimension grouped quads,
13
+ * `[p0_0, p1_0, m0_0, m1_0, p0_1, p1_1, m0_1, m1_1, ..., m1_{dim-1}]`,
14
+ * length `4 * dim`. dim 1.
15
+ *
16
+ * Internally dispatched by dimension; each path enumerates different sets
17
+ * of candidate critical points (see the variant docs for what each reports
18
+ * and how many entries the result buffer needs):
19
+ * - 1D: range overlap or extrema-based closest pair → 1 pair.
20
+ * - ND (≥ 2): grid-Newton interior critical points + boundary minima up
21
+ * to 89 pairs (~178 floats). dim === 2 uses this path too; the former
22
+ * bespoke 2D Bezout specialization was removed because it missed interior
23
+ * non-intersection closest approaches.
24
+ *
25
+ * Required buffer size: caller must reserve enough space for the worst
26
+ * case of the dim it intends to call with. Reserving ≥ 178 floats past
27
+ * `result_offset` covers all dims.
28
+ *
29
+ * @param {Float64Array|number[]} a length 4*dim
30
+ * @param {Float64Array|number[]} b length 4*dim
31
+ * @param {number} dim
32
+ * @param {Float64Array|number[]} result
33
+ * @param {number} result_offset
34
+ * @returns {number} number of (s, t) pairs written
35
+ */
36
+ export function spline3_hermite_intersection_spline3_hermite(
37
+ a, b, dim,
38
+ result, result_offset
39
+ ) {
40
+ assert.greaterThanOrEqual(dim, 1, 'dim');
41
+
42
+ if (dim === 1) {
43
+ return spline3_hermite_intersection_spline3_hermite_1d(a, b, result, result_offset);
44
+ }
45
+
46
+ // dim >= 2 (including 2D) goes through the dimension-agnostic ND path.
47
+ return spline3_hermite_intersection_spline3_hermite_nd(a, b, dim, result, result_offset);
48
+ }
@@ -1,31 +1,33 @@
1
1
  /**
2
- * Critical-point enumerator for the 2D Hermite curve-pair intersection
3
- * problem. Writes (s, t) pairs sequentially into `result` starting at
4
- * `result_offset` and returns the count.
2
+ * @deprecated The dedicated 2D Bezout specialization has been removed. It
3
+ * enumerated interior *intersections* (via the (3,3)/(3,3) Bezout resultant)
4
+ * plus the four boundary-edge minima only — it never enumerated interior
5
+ * *non-intersection* critical points, so it silently missed the closest
6
+ * approach of two non-intersecting cubic arcs whenever that approach is
7
+ * interior-to-interior (wrong on ~27% of random curve pairs, by as much as
8
+ * several units of squared distance). Its premise — "no interior intersection
9
+ * ⇒ the global minimum lies on the boundary" — is false for cubic arcs.
5
10
  *
6
- * What is reported:
7
- * - All interior true intersections found by the Bezout resultant (up to 9).
8
- * - Four boundary closest-approach pairs, one per edge of [0,1]²:
9
- * (0, t*) with t* minimising ‖A(0) B(t)‖²
10
- * (1, t*) with t* minimising ‖A(1) B(t)‖²
11
- * (s*, 0) with s* minimising ‖A(s) − B(0)‖²
12
- * (s*, 1) with s* minimising ‖A(s) − B(1)‖²
13
- * Each `nearest_t_on_curve` call internally tests endpoints, so corners
14
- * are folded into these as appropriate.
11
+ * This symbol is retained only so existing callers keep working; it now
12
+ * forwards to the dimension-agnostic
13
+ * {@link spline3_hermite_intersection_spline3_hermite_nd} path, which
14
+ * enumerates interior critical points (grid-seeded 2D Newton) in addition to
15
+ * the boundary minima and is correct for the full closest-approach problem.
15
16
  *
16
- * Caller is responsible for evaluating both curves at each (s, t) and picking
17
- * whichever pair(s) they care about (closest, within threshold, etc.).
17
+ * Prefer the dimension dispatcher
18
+ * {@link spline3_hermite_intersection_spline3_hermite} or
19
+ * `spline3_hermite_intersection_spline3_hermite_nd` directly. Note the buffer
20
+ * requirement is now the ND worst case: `result.length >= result_offset +
21
+ * 2 * ND_MAX_ROOTS` floats (currently 178), not the old 2D bound.
18
22
  *
19
- * Typical buffer size: at most 9 interior + 4 boundary = 13 pairs →
20
- * `result.length >= result_offset + 26`. in degenerate cases up to 89 pairs.
21
- *
22
- * Edge case: if the Bezout resultant is numerically degenerate (one of the
23
- * input curves is constant on an axis, etc.), the routine forwards to the
24
- * dimension-agnostic ND path.
23
+ * Critical-point enumerator for the 2D Hermite curve-pair problem. Writes
24
+ * (s, t) pairs sequentially into `result` starting at `result_offset` and
25
+ * returns the count of pairs written. The caller evaluates both curves at each
26
+ * pair and picks the one(s) it cares about (closest, within threshold, etc.).
25
27
  *
26
28
  * @param {Float64Array|number[]} a length 8: [a_p0_x, a_p1_x, a_m0_x, a_m1_x, a_p0_y, a_p1_y, a_m0_y, a_m1_y]
27
29
  * @param {Float64Array|number[]} b length 8 (same layout)
28
- * @param {Float64Array|number[]} result length >= result_offset + 198
30
+ * @param {Float64Array|number[]} result length >= result_offset + 2 * ND_MAX_ROOTS
29
31
  * @param {number} result_offset
30
32
  * @returns {number} number of (s, t) pairs written
31
33
  */
@@ -1 +1 @@
1
- {"version":3,"file":"spline3_hermite_intersection_spline3_hermite_2d.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.js"],"names":[],"mappings":"AA+SA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,mEANW,YAAY,GAAC,MAAM,EAAE,KACrB,YAAY,GAAC,MAAM,EAAE,UACrB,YAAY,GAAC,MAAM,EAAE,iBACrB,MAAM,GACJ,MAAM,CAyElB"}
1
+ {"version":3,"file":"spline3_hermite_intersection_spline3_hermite_2d.d.ts","sourceRoot":"","sources":["../../../../../src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,mEANW,YAAY,GAAC,MAAM,EAAE,KACrB,YAAY,GAAC,MAAM,EAAE,UACrB,YAAY,GAAC,MAAM,EAAE,iBACrB,MAAM,GACJ,MAAM,CAOlB"}
@@ -1,406 +1,41 @@
1
- import { cubic_residual_times_derivative_accumulate } from "../linalg/cubic_residual_times_derivative_accumulate.js";
2
- import { polynomial_add_into } from "../linalg/polynomial_add_into.js";
3
- import { polynomial_cubic_horner_eval } from "../linalg/polynomial_cubic_horner_eval.js";
4
- import { polynomial_multiply } from "../linalg/polynomial_multiply.js";
5
- import { polynomial_real_roots_in_interval } from "../linalg/polynomial_real_roots_in_interval.js";
6
- import { polynomial_scale_into } from "../linalg/polynomial_scale_into.js";
7
- import { polynomial_sub_into } from "../linalg/polynomial_sub_into.js";
8
- import { solveCubic } from "../solveCubic.js";
9
- import { spline3_hermite } from "./spline3_hermite.js";
10
- import { spline3_hermite_intersection_spline3_hermite_nd } from "./spline3_hermite_intersection_spline3_hermite_nd.js";
11
- import { spline3_hermite_to_monomial } from "./spline3_hermite_to_monomial.js";
12
-
13
- /*
14
- 2D specialization. Two equations (one per axis), each bidegree (3,3) in (s,t):
15
-
16
- P(s, t) = A_x(s) B_x(t) = 0
17
- Q(s, t) = A_y(s) B_y(t) = 0
18
-
19
- Eliminate t through the Bezout 3×3 resultant matrix (smaller and easier than
20
- the 6×6 Sylvester form). The resultant det(Bezout) is a univariate polynomial
21
- in s of degree ≤ 9. Its real roots in [0, 1] give the s-coordinates of all
22
- true intersections; for each, the corresponding t is recovered by solving the
23
- cubic A_x(s_i) B_x(t) = 0 and verifying agreement on the y component.
24
-
25
- Closest-approach handling:
26
- - Any true interior intersection distance² = 0; report it.
27
- - Otherwise, the global minimum of squared distance over [0,1]² lies on the
28
- boundary: an edge minimum (one parameter pinned at 0 or 1, the other a
29
- quintic minimisation reduced to a quartic root-finding) or a corner.
30
-
31
- Polynomial-coefficient buffers and the small ad-hoc polynomial arithmetic
32
- used to assemble det(Bezout) are kept private to this module — they are
33
- implementation glue, not a general "polynomial library".
34
- */
35
-
36
- // Per-curve monomial buffers (4 coeffs per axis).
37
- const _A_x_mono = new Float64Array(4);
38
- const _A_y_mono = new Float64Array(4);
39
- const _B_x_mono = new Float64Array(4);
40
- const _B_y_mono = new Float64Array(4);
41
-
42
- // Bezout entries. The four polynomial-valued ones are degree ≤ 3 in s; the two
43
- // scalar-valued ones are stored as plain numbers in `_bz_const`.
44
- // _bz_poly[0] = B[0,0] (degree 3 in s)
45
- // _bz_poly[1] = B[0,1]
46
- // _bz_poly[2] = B[0,2]
47
- // _bz_poly[3] = B[1,1]
48
- const _bz_poly = [
49
- new Float64Array(4),
50
- new Float64Array(4),
51
- new Float64Array(4),
52
- new Float64Array(4),
53
- ];
54
- // _bz_const[0] = B[1,2], _bz_const[1] = B[2,2]
55
- const _bz_const = new Float64Array(2);
56
-
57
- // Resultant coefficients (degree ≤ 9 → 10 slots).
58
- const _resultant = new Float64Array(10);
59
-
60
- // Scratch polynomials used while computing det(Bezout).
61
- const _tmp_poly_a = new Float64Array(10);
62
- const _tmp_poly_b = new Float64Array(10);
63
- const _tmp_poly_c = new Float64Array(10);
64
- const _tmp_poly_d = new Float64Array(10);
65
-
66
- const _root_buffer_s = new Float64Array(9);
67
- const _root_buffer_t = new Float64Array(3);
68
-
69
- const Y_MATCH_TOLERANCE = 1e-7;
70
-
71
- // ── core algorithm helpers ────────────────────────────────────────────────
72
-
73
- /**
74
- * Build the 6 Bezout entries (4 polynomial, 2 constant) from the monomial
75
- * coefficients of A and B. See module preamble for the algebraic derivation.
76
- *
77
- * Layout:
78
- * a-side polys are A_*(s) in monomial basis (cubic in s). Their coefficient
79
- * arrays here are length 4 (constant ... s³).
80
- * b-side polys are B_*(t) — we only need the *coefficients* (constants
81
- * wrt s) in the bivariate form P(s,t) = A_x(s) − B_x(t). So β_kx = the k-th
82
- * monomial coefficient of B_x.
83
- */
84
- function build_bezout_entries(a_x, a_y, b_x, b_y) {
85
- // Coefficients of P(s, t) viewed as polynomial in t with s-poly coeffs:
86
- // p_0(s) = A_x(s) − β0_x (cubic in s)
87
- // p_1 = -β1_x (constant)
88
- // p_2 = -β2_x
89
- // p_3 = -β3_x
90
- // and similarly q_k for Q.
91
-
92
- const beta_1x = -b_x[1];
93
- const beta_2x = -b_x[2];
94
- const beta_3x = -b_x[3];
95
-
96
- const beta_1y = -b_y[1];
97
- const beta_2y = -b_y[2];
98
- const beta_3y = -b_y[3];
99
-
100
- // p_0(s) = A_x(s) - β0_x
101
- const p0 = _tmp_poly_a;
102
- p0[0] = a_x[0] - b_x[0];
103
- p0[1] = a_x[1];
104
- p0[2] = a_x[2];
105
- p0[3] = a_x[3];
106
-
107
- // q_0(s) = A_y(s) - β0_y
108
- const q0 = _tmp_poly_b;
109
- q0[0] = a_y[0] - b_y[0];
110
- q0[1] = a_y[1];
111
- q0[2] = a_y[2];
112
- q0[3] = a_y[3];
113
-
114
- // B[0,0] = a_1 b_0 - a_0 b_1 = beta_1x * q_0(s) - p_0(s) * beta_1y
115
- const b00 = _bz_poly[0];
116
- for (let i = 0; i < 4; i++){
117
- b00[i] = beta_1x * q0[i] - p0[i] * beta_1y;
118
- }
119
-
120
- // B[0,1] = a_2 b_0 - a_0 b_2 = beta_2x * q_0(s) - p_0(s) * beta_2y
121
- const b01 = _bz_poly[1];
122
- for (let i = 0; i < 4; i++){
123
- b01[i] = beta_2x * q0[i] - p0[i] * beta_2y;
124
- }
125
-
126
- // B[0,2] = a_3 b_0 - a_0 b_3 = beta_3x * q_0(s) - p_0(s) * beta_3y
127
- const b02 = _bz_poly[2];
128
- for (let i = 0; i < 4; i++){
129
- b02[i] = beta_3x * q0[i] - p0[i] * beta_3y;
130
- }
131
-
132
- // B[1,1] = (a_3 b_0 - a_0 b_3) + (a_2 b_1 - a_1 b_2)
133
- // = B[0,2] + (beta_2x * beta_1y - beta_1x * beta_2y)
134
- const b11 = _bz_poly[3];
135
- const inner = beta_2x * beta_1y - beta_1x * beta_2y;
136
- for (let i = 0; i < 4; i++){
137
- b11[i] = b02[i];
138
- }
139
-
140
- b11[0] += inner;
141
-
142
- // B[1,2] = a_3 b_1 - a_1 b_3 = beta_3x * beta_1y - beta_1x * beta_3y (constant)
143
- _bz_const[0] = beta_3x * beta_1y - beta_1x * beta_3y;
144
-
145
- // B[2,2] = a_3 b_2 - a_2 b_3 = beta_3x * beta_2y - beta_2x * beta_3y (constant)
146
- _bz_const[1] = beta_3x * beta_2y - beta_2x * beta_3y;
147
- }
148
-
149
- /**
150
- * Compute det(Bezout) as a polynomial in s and write its coefficients into
151
- * `_resultant`. Returns the (effective) degree + 1 — i.e. the number of
152
- * coefficients that are meaningful.
153
- *
154
- * Closed form: det(B) = B[0,2]·(B[0,1]·B[1,2] − B[0,2]·B[1,1])
155
- * − B[1,2]·(B[0,0]·B[1,2] − B[0,2]·B[0,1])
156
- * + B[2,2]·(B[0,0]·B[1,1] − B[0,1]²)
157
- *
158
- * B[0,0], B[0,1], B[0,2], B[1,1] are cubic polynomials in s (length 4).
159
- * B[1,2], B[2,2] are constants (length 1).
160
- */
161
- function compute_resultant() {
162
- const b00 = _bz_poly[0]; // length 4
163
- const b01 = _bz_poly[1]; // length 4
164
- const b02 = _bz_poly[2]; // length 4
165
- const b11 = _bz_poly[3]; // length 4
166
- const b12 = _bz_const[0];
167
- const b22 = _bz_const[1];
168
-
169
- _resultant.fill(0);
170
-
171
- // ── Term A: B[0,2] · (B[0,1] · B[1,2] − B[0,2] · B[1,1]) ──
172
- // tmp_c = B[0,1] · B[1,2] (cubic × constant → cubic, length 4)
173
- const tmp_c = _tmp_poly_c;
174
- let tmp_c_len = 4;
175
-
176
- for (let i = 0; i < 4; i++){
177
- tmp_c[i] = b01[i] * b12;
178
- }
179
-
180
- // tmp_d = B[0,2] · B[1,1] (cubic × cubic → degree 6, length 7)
181
- const tmp_d = _tmp_poly_d;
182
- const tmp_d_len = polynomial_multiply(tmp_d, b02, 4, b11, 4);
183
-
184
- // tmp_c -= tmp_d (length 7)
185
- tmp_c_len = polynomial_sub_into(tmp_c, tmp_c_len, tmp_d, tmp_d_len);
186
-
187
- // term_a = B[0,2] · tmp_c (length 4 + 7 − 1 = 10)
188
- const tmp_a = _tmp_poly_a;
189
- const tmp_a_len = polynomial_multiply(tmp_a, b02, 4, tmp_c, tmp_c_len);
190
-
191
- // resultant += term_a
192
- polynomial_add_into(_resultant, 0, tmp_a, tmp_a_len);
193
-
194
- // ── Term B: −B[1,2] · (B[0,0] · B[1,2] − B[0,2] · B[0,1]) ──
195
- // tmp_c = B[0,0] · B[1,2] (cubic × constant)
196
- tmp_c_len = 4;
197
- for (let i = 0; i < 4; i++) tmp_c[i] = b00[i] * b12;
198
-
199
- // tmp_d = B[0,2] · B[0,1] (cubic × cubic → length 7)
200
- const tmp_d_len_2 = polynomial_multiply(tmp_d, b02, 4, b01, 4);
201
-
202
- tmp_c_len = polynomial_sub_into(tmp_c, tmp_c_len, tmp_d, tmp_d_len_2);
203
-
204
- polynomial_scale_into(tmp_c, tmp_c_len, -b12);
205
- polynomial_add_into(_resultant, _resultant.length, tmp_c, tmp_c_len);
206
-
207
- // ── Term C: B[2,2] · (B[0,0] · B[1,1] − B[0,1]²) ──
208
- const tmp_d_len_3 = polynomial_multiply(tmp_d, b00, 4, b11, 4); // length 7
209
- tmp_c_len = polynomial_multiply(tmp_c, b01, 4, b01, 4); // length 7
210
-
211
- polynomial_sub_into(tmp_d, tmp_d_len_3, tmp_c, tmp_c_len);
212
-
213
- polynomial_scale_into(tmp_d, tmp_d_len_3, b22);
214
- polynomial_add_into(_resultant, _resultant.length, tmp_d, tmp_d_len_3);
215
- }
216
-
217
- /**
218
- * For a given s, find a t in [0,1] such that A_x(s) − B_x(t) = 0 AND
219
- * A_y(s) ≈ B_y(t) (the latter within Y_MATCH_TOLERANCE). Returns the matched
220
- * t, or -1 if none.
221
- */
222
- function recover_t(a, b, s) {
223
- // A_x(s) value:
224
- const a_x_at_s = spline3_hermite(s, a[0], a[1], a[2], a[3]);
225
- const a_y_at_s = spline3_hermite(s, a[4], a[5], a[6], a[7]);
226
-
227
- // Solve B_x(t) = a_x_at_s for t:
228
- // B_x in monomial: β0_x + β1_x t + β2_x t² + β3_x t³ - a_x_at_s = 0
229
- const t_count = solveCubic(
230
- _root_buffer_t, 0,
231
- _B_x_mono[3], _B_x_mono[2], _B_x_mono[1], _B_x_mono[0] - a_x_at_s
232
- );
233
-
234
- let best_t = -1;
235
- let best_dy = Number.POSITIVE_INFINITY;
236
-
237
- for (let i = 0; i < t_count; i++) {
238
- const t = _root_buffer_t[i];
239
-
240
- if (t < 0 || t > 1) continue;
241
-
242
- const b_y_at_t = spline3_hermite(t, b[4], b[5], b[6], b[7]);
243
- const dy = Math.abs(a_y_at_s - b_y_at_t);
244
-
245
- if (dy < best_dy) {
246
- best_dy = dy;
247
- best_t = t;
248
- }
249
-
250
- }
251
-
252
- if (best_dy > Y_MATCH_TOLERANCE) return -1;
253
- return best_t;
254
- }
255
-
256
- /**
257
- * Find the minimum of ||point − B(t)||² over t in [0,1], where `point` is a 2D
258
- * point (px, py). Solves the quintic ∂/∂t = 0 by feeding it to
259
- * polynomial_real_roots_in_interval and tests each root + the two endpoints.
260
- *
261
- * Writes `[t, dist²]` into the returned 2-element module scratch.
262
- */
263
- const _edge_tmp_quintic = new Float64Array(6);
264
- const _edge_tmp_roots = new Float64Array(5);
265
- const _edge_result = new Float64Array(2);
266
-
267
- function nearest_t_on_curve(b_mono_x, b_mono_y, point_x, point_y) {
268
- // ‖B(t) − point‖² is degree 6 in t. Its derivative is degree 5 — the
269
- // shared accumulator builds it per axis, dropping the constant factor of
270
- // 2 since it doesn't affect root locations.
271
-
272
- const quintic = _edge_tmp_quintic;
273
- for (let i = 0; i < 6; i++) {
274
- quintic[i] = 0;
275
- }
276
-
277
- cubic_residual_times_derivative_accumulate(quintic, b_mono_x, 0, point_x);
278
- cubic_residual_times_derivative_accumulate(quintic, b_mono_y, 0, point_y);
279
-
280
- const root_count = polynomial_real_roots_in_interval(
281
- quintic, 5, 0, 1, _edge_tmp_roots, 0
282
- );
283
-
284
- let best_t = 0;
285
- let best_d2 = Number.POSITIVE_INFINITY;
286
-
287
- for (let i = -2; i < root_count; i++) {
288
- // i = -2 → t = 0; i = -1 → t = 1; otherwise interior roots
289
- const t = i === -2 ? 0 : (i === -1 ? 1 : _edge_tmp_roots[i]);
290
- const ex = polynomial_cubic_horner_eval(b_mono_x, 0, t) - point_x;
291
- const ey = polynomial_cubic_horner_eval(b_mono_y, 0, t) - point_y;
292
- const d2 = ex * ex + ey * ey;
293
- if (d2 < best_d2) {
294
- best_d2 = d2;
295
- best_t = t;
296
- }
297
- }
298
-
299
- _edge_result[0] = best_t;
300
- _edge_result[1] = best_d2;
301
- return _edge_result;
302
- }
303
-
304
- /**
305
- * Critical-point enumerator for the 2D Hermite curve-pair intersection
306
- * problem. Writes (s, t) pairs sequentially into `result` starting at
307
- * `result_offset` and returns the count.
308
- *
309
- * What is reported:
310
- * - All interior true intersections found by the Bezout resultant (up to 9).
311
- * - Four boundary closest-approach pairs, one per edge of [0,1]²:
312
- * (0, t*) with t* minimising ‖A(0) − B(t)‖²
313
- * (1, t*) with t* minimising ‖A(1) − B(t)‖²
314
- * (s*, 0) with s* minimising ‖A(s) − B(0)‖²
315
- * (s*, 1) with s* minimising ‖A(s) − B(1)‖²
316
- * Each `nearest_t_on_curve` call internally tests endpoints, so corners
317
- * are folded into these as appropriate.
318
- *
319
- * Caller is responsible for evaluating both curves at each (s, t) and picking
320
- * whichever pair(s) they care about (closest, within threshold, etc.).
321
- *
322
- * Typical buffer size: at most 9 interior + 4 boundary = 13 pairs →
323
- * `result.length >= result_offset + 26`. in degenerate cases up to 89 pairs.
324
- *
325
- * Edge case: if the Bezout resultant is numerically degenerate (one of the
326
- * input curves is constant on an axis, etc.), the routine forwards to the
327
- * dimension-agnostic ND path.
328
- *
329
- * @param {Float64Array|number[]} a length 8: [a_p0_x, a_p1_x, a_m0_x, a_m1_x, a_p0_y, a_p1_y, a_m0_y, a_m1_y]
330
- * @param {Float64Array|number[]} b length 8 (same layout)
331
- * @param {Float64Array|number[]} result length >= result_offset + 198
332
- * @param {number} result_offset
333
- * @returns {number} number of (s, t) pairs written
334
- */
335
- export function spline3_hermite_intersection_spline3_hermite_2d(
336
- a, b,
337
- result, result_offset
338
- ) {
339
- spline3_hermite_to_monomial(_A_x_mono, 0, 1, a[0], a[1], a[2], a[3]);
340
- spline3_hermite_to_monomial(_A_y_mono, 0, 1, a[4], a[5], a[6], a[7]);
341
- spline3_hermite_to_monomial(_B_x_mono, 0, 1, b[0], b[1], b[2], b[3]);
342
- spline3_hermite_to_monomial(_B_y_mono, 0, 1, b[4], b[5], b[6], b[7]);
343
-
344
- build_bezout_entries(_A_x_mono, _A_y_mono, _B_x_mono, _B_y_mono);
345
- compute_resultant();
346
-
347
- // Detect a degenerate resultant. This happens when one (or both) input
348
- // curves is degree < 3 in its parameter — straight axis-aligned lines are
349
- // the canonical example. The (3,3)/(3,3) Bezout construction collapses
350
- // because the relevant leading coefficients are zero. In that case, fall
351
- // back to the dimension-agnostic Newton-from-grid path.
352
- let max_resultant_mag = 0;
353
- for (let i = 0; i < _resultant.length; i++) {
354
- const v = Math.abs(_resultant[i]);
355
- if (v > max_resultant_mag) max_resultant_mag = v;
356
- }
357
- if (max_resultant_mag < 1e-12) {
358
- return spline3_hermite_intersection_spline3_hermite_nd(a, b, 2, result, result_offset);
359
- }
360
-
361
- const s_root_count = polynomial_real_roots_in_interval(
362
- _resultant, 9, 0, 1, _root_buffer_s, 0
363
- );
364
-
365
- let write = result_offset;
366
-
367
- // Interior intersections (true: distance² = 0).
368
- for (let i = 0; i < s_root_count; i++) {
369
- const s = _root_buffer_s[i];
370
- const t = recover_t(a, b, s);
371
- if (t >= 0) {
372
- result[write++] = s;
373
- result[write++] = t;
374
- }
375
- }
376
-
377
- // Boundary closest-approach pairs — one per edge of [0,1]². Each call
378
- // internally tests the edge endpoints (corners) and reports the minimum.
379
-
380
- // Edge s = 0: minimise ‖A(0) − B(t)‖² over t.
381
- {
382
- const r = nearest_t_on_curve(_B_x_mono, _B_y_mono, a[0], a[4]);
383
- result[write++] = 0;
384
- result[write++] = r[0];
385
- }
386
- // Edge s = 1.
387
- {
388
- const r = nearest_t_on_curve(_B_x_mono, _B_y_mono, a[1], a[5]);
389
- result[write++] = 1;
390
- result[write++] = r[0];
391
- }
392
- // Edge t = 0: minimise ‖A(s) − B(0)‖² over s.
393
- {
394
- const r = nearest_t_on_curve(_A_x_mono, _A_y_mono, b[0], b[4]);
395
- result[write++] = r[0];
396
- result[write++] = 0;
397
- }
398
- // Edge t = 1.
399
- {
400
- const r = nearest_t_on_curve(_A_x_mono, _A_y_mono, b[1], b[5]);
401
- result[write++] = r[0];
402
- result[write++] = 1;
403
- }
404
-
405
- return (write - result_offset) >> 1;
406
- }
1
+ import { spline3_hermite_intersection_spline3_hermite_nd } from "./spline3_hermite_intersection_spline3_hermite_nd.js";
2
+
3
+ /**
4
+ * @deprecated The dedicated 2D Bezout specialization has been removed. It
5
+ * enumerated interior *intersections* (via the (3,3)/(3,3) Bezout resultant)
6
+ * plus the four boundary-edge minima only — it never enumerated interior
7
+ * *non-intersection* critical points, so it silently missed the closest
8
+ * approach of two non-intersecting cubic arcs whenever that approach is
9
+ * interior-to-interior (wrong on ~27% of random curve pairs, by as much as
10
+ * several units of squared distance). Its premise — "no interior intersection
11
+ * the global minimum lies on the boundary" — is false for cubic arcs.
12
+ *
13
+ * This symbol is retained only so existing callers keep working; it now
14
+ * forwards to the dimension-agnostic
15
+ * {@link spline3_hermite_intersection_spline3_hermite_nd} path, which
16
+ * enumerates interior critical points (grid-seeded 2D Newton) in addition to
17
+ * the boundary minima and is correct for the full closest-approach problem.
18
+ *
19
+ * Prefer the dimension dispatcher
20
+ * {@link spline3_hermite_intersection_spline3_hermite} or
21
+ * `spline3_hermite_intersection_spline3_hermite_nd` directly. Note the buffer
22
+ * requirement is now the ND worst case: `result.length >= result_offset +
23
+ * 2 * ND_MAX_ROOTS` floats (currently 178), not the old 2D bound.
24
+ *
25
+ * Critical-point enumerator for the 2D Hermite curve-pair problem. Writes
26
+ * (s, t) pairs sequentially into `result` starting at `result_offset` and
27
+ * returns the count of pairs written. The caller evaluates both curves at each
28
+ * pair and picks the one(s) it cares about (closest, within threshold, etc.).
29
+ *
30
+ * @param {Float64Array|number[]} a length 8: [a_p0_x, a_p1_x, a_m0_x, a_m1_x, a_p0_y, a_p1_y, a_m0_y, a_m1_y]
31
+ * @param {Float64Array|number[]} b length 8 (same layout)
32
+ * @param {Float64Array|number[]} result length >= result_offset + 2 * ND_MAX_ROOTS
33
+ * @param {number} result_offset
34
+ * @returns {number} number of (s, t) pairs written
35
+ */
36
+ export function spline3_hermite_intersection_spline3_hermite_2d(
37
+ a, b,
38
+ result, result_offset
39
+ ) {
40
+ return spline3_hermite_intersection_spline3_hermite_nd(a, b, 2, result, result_offset);
41
+ }