@woosh/meep-engine 2.139.0 → 2.141.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 (199) hide show
  1. package/package.json +1 -1
  2. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts +3 -3
  3. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts.map +1 -1
  4. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js +4 -4
  5. package/src/{engine/physics/broadphase/aabb_transform_oriented.d.ts → core/geom/3d/aabb/aabb3_transform_oriented.d.ts} +2 -2
  6. package/src/core/geom/3d/aabb/aabb3_transform_oriented.d.ts.map +1 -0
  7. package/src/{engine/physics/broadphase/aabb_transform_oriented.js → core/geom/3d/aabb/aabb3_transform_oriented.js} +1 -1
  8. package/src/core/geom/3d/quaternion/quat3_multiply.d.ts +21 -0
  9. package/src/core/geom/3d/quaternion/quat3_multiply.d.ts.map +1 -0
  10. package/src/core/geom/3d/quaternion/quat3_multiply.js +25 -0
  11. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts +54 -0
  12. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts.map +1 -0
  13. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -0
  14. package/src/core/geom/3d/shape/AbstractShape3D.d.ts +24 -2
  15. package/src/core/geom/3d/shape/AbstractShape3D.d.ts.map +1 -1
  16. package/src/core/geom/3d/shape/AbstractShape3D.js +24 -1
  17. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +148 -0
  18. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -0
  19. package/src/core/geom/3d/shape/HeightMapShape3D.js +451 -0
  20. package/src/core/geom/3d/shape/MeshShape3D.d.ts +210 -0
  21. package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -0
  22. package/src/core/geom/3d/shape/MeshShape3D.js +593 -0
  23. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  24. package/src/core/geom/3d/shape/TransformedShape3D.js +46 -2
  25. package/src/core/geom/3d/shape/Triangle3D.d.ts +95 -0
  26. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -0
  27. package/src/core/geom/3d/shape/Triangle3D.js +318 -0
  28. package/src/core/geom/3d/shape/UnionShape3D.js +13 -0
  29. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts +30 -0
  30. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts.map +1 -0
  31. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +64 -0
  32. package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.js +9 -11
  33. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts +28 -0
  34. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts.map +1 -0
  35. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -0
  36. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.d.ts.map +1 -1
  37. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +40 -18
  38. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts +9 -5
  39. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts.map +1 -1
  40. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +38 -10
  41. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts +14 -5
  42. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts.map +1 -1
  43. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +47 -5
  44. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts +19 -0
  45. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts.map +1 -1
  46. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +75 -13
  47. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts +2 -2
  48. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts.map +1 -1
  49. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +1 -1
  50. package/src/core/geom/vec3/v3_dot_array_array.d.ts +3 -3
  51. package/src/core/geom/vec3/v3_dot_array_array.d.ts.map +1 -1
  52. package/src/core/geom/vec3/v3_dot_array_array.js +2 -2
  53. package/src/core/geom/vec3/v3_negate_array.d.ts +3 -3
  54. package/src/core/geom/vec3/v3_negate_array.d.ts.map +1 -1
  55. package/src/core/geom/vec3/v3_negate_array.js +2 -2
  56. package/src/core/geom/vec3/v3_quat3_apply.d.ts +29 -0
  57. package/src/core/geom/vec3/v3_quat3_apply.d.ts.map +1 -0
  58. package/src/core/geom/vec3/v3_quat3_apply.js +39 -0
  59. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts +30 -0
  60. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts.map +1 -0
  61. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +41 -0
  62. package/src/core/geom/vec3/v3_triple_cross_product.d.ts +32 -0
  63. package/src/core/geom/vec3/v3_triple_cross_product.d.ts.map +1 -0
  64. package/src/core/geom/vec3/v3_triple_cross_product.js +45 -0
  65. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +16 -3
  66. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  67. package/src/engine/control/first-person/FirstPersonPlayerController.js +211 -211
  68. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +72 -8
  69. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  70. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +37 -5
  71. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +101 -3
  72. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  73. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1789 -1416
  74. package/src/engine/control/first-person/TODO.md +173 -127
  75. package/src/engine/control/first-person/abilities/Slide.d.ts.map +1 -1
  76. package/src/engine/control/first-person/abilities/Slide.js +9 -1
  77. package/src/engine/control/first-person/prototype_first_person_controller.js +88 -2
  78. package/src/engine/control/first-person/test/buildTestPlayer.d.ts.map +1 -1
  79. package/src/engine/control/first-person/test/buildTestPlayer.js +9 -1
  80. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts +42 -0
  81. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts.map +1 -0
  82. package/src/engine/graphics/geometry/CapsuleGeometry.js +171 -0
  83. package/src/engine/physics/BULLET_REVIEW.md +945 -0
  84. package/src/engine/physics/CANNON_REVIEW.md +1300 -0
  85. package/src/engine/physics/JOLT_REVIEW.md +913 -0
  86. package/src/engine/physics/PLAN.md +578 -236
  87. package/src/engine/physics/RAPIER_REVIEW.md +934 -0
  88. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -0
  89. package/src/engine/physics/REVIEW_002.md +151 -0
  90. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +2 -2
  91. package/src/engine/physics/constraint/DofMode.d.ts +28 -0
  92. package/src/engine/physics/constraint/DofMode.d.ts.map +1 -0
  93. package/src/engine/physics/constraint/DofMode.js +35 -0
  94. package/src/engine/physics/constraint/solve_constraints.d.ts +16 -0
  95. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -0
  96. package/src/engine/physics/constraint/solve_constraints.js +436 -0
  97. package/src/engine/physics/contact/ManifoldStore.d.ts +83 -10
  98. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  99. package/src/engine/physics/contact/ManifoldStore.js +608 -499
  100. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts +2 -2
  101. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts.map +1 -1
  102. package/src/engine/physics/ecs/Joint.d.ts +179 -0
  103. package/src/engine/physics/ecs/Joint.d.ts.map +1 -0
  104. package/src/engine/physics/ecs/Joint.js +234 -0
  105. package/src/engine/physics/ecs/PhysicsSystem.d.ts +180 -20
  106. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  107. package/src/engine/physics/ecs/PhysicsSystem.js +1423 -1159
  108. package/src/engine/physics/fluid/FluidField.d.ts +14 -10
  109. package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
  110. package/src/engine/physics/fluid/FluidField.js +14 -10
  111. package/src/engine/physics/fluid/FluidSimulator.js +1 -1
  112. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts +17 -10
  113. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.d.ts.map +1 -1
  114. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +18 -11
  115. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts +13 -10
  116. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.d.ts.map +1 -1
  117. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +18 -13
  118. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts +4 -3
  119. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.d.ts.map +1 -1
  120. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +15 -11
  121. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +30 -6
  122. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -1
  123. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +44 -18
  124. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +6 -6
  125. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +1 -1
  126. package/src/engine/physics/gjk/expanding_polytope_algorithm.js +68 -22
  127. package/src/engine/physics/gjk/gjk.d.ts +28 -2
  128. package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
  129. package/src/engine/physics/gjk/gjk.js +421 -378
  130. package/src/engine/physics/gjk/minkowski_support.d.ts +37 -0
  131. package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -0
  132. package/src/engine/physics/gjk/minkowski_support.js +75 -0
  133. package/src/engine/physics/gjk/mpr.d.ts +56 -0
  134. package/src/engine/physics/gjk/mpr.d.ts.map +1 -0
  135. package/src/engine/physics/gjk/mpr.js +344 -0
  136. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +20 -5
  137. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  138. package/src/engine/physics/inertia/world_inverse_inertia.js +36 -38
  139. package/src/engine/physics/integration/integrate_position.d.ts +25 -7
  140. package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
  141. package/src/engine/physics/integration/integrate_position.js +43 -12
  142. package/src/engine/physics/integration/integrate_velocity.d.ts +30 -0
  143. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
  144. package/src/engine/physics/integration/integrate_velocity.js +82 -1
  145. package/src/engine/physics/island/IslandBuilder.d.ts +4 -1
  146. package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -1
  147. package/src/engine/physics/island/IslandBuilder.js +33 -16
  148. package/src/engine/physics/narrowphase/PosedShape.d.ts +0 -8
  149. package/src/engine/physics/narrowphase/PosedShape.d.ts.map +1 -1
  150. package/src/engine/physics/narrowphase/PosedShape.js +28 -30
  151. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  152. package/src/engine/physics/narrowphase/box_box_manifold.js +140 -18
  153. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts +30 -0
  154. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -0
  155. package/src/engine/physics/narrowphase/box_triangle_contact.js +811 -0
  156. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  157. package/src/engine/physics/narrowphase/capsule_contacts.js +10 -56
  158. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts +71 -0
  159. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts.map +1 -0
  160. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +375 -0
  161. package/src/engine/physics/narrowphase/compute_penetration.d.ts +91 -0
  162. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -0
  163. package/src/engine/physics/narrowphase/compute_penetration.js +396 -0
  164. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts +35 -0
  165. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts.map +1 -0
  166. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.js +80 -0
  167. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts +31 -0
  168. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts.map +1 -0
  169. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -0
  170. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +42 -0
  171. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -0
  172. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +204 -0
  173. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts +42 -0
  174. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -0
  175. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +94 -0
  176. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts +37 -0
  177. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts.map +1 -0
  178. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -0
  179. package/src/engine/physics/narrowphase/narrowphase_step.d.ts +41 -2
  180. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  181. package/src/engine/physics/narrowphase/narrowphase_step.js +1497 -382
  182. package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
  183. package/src/engine/physics/narrowphase/sphere_box_contact.js +16 -23
  184. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts +48 -0
  185. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts.map +1 -0
  186. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +143 -0
  187. package/src/engine/physics/queries/overlap_shape.d.ts +51 -0
  188. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -0
  189. package/src/engine/physics/queries/overlap_shape.js +183 -0
  190. package/src/engine/physics/queries/shape_cast.d.ts +56 -0
  191. package/src/engine/physics/queries/shape_cast.d.ts.map +1 -0
  192. package/src/engine/physics/queries/shape_cast.js +387 -0
  193. package/src/engine/physics/solver/solve_contacts.d.ts +146 -32
  194. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  195. package/src/engine/physics/solver/solve_contacts.js +809 -223
  196. package/src/engine/physics/broadphase/aabb_transform_oriented.d.ts.map +0 -1
  197. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts +0 -20
  198. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts.map +0 -1
  199. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.js +0 -83
@@ -0,0 +1,436 @@
1
+ import { BodyKind } from "../ecs/BodyKind.js";
2
+ import { SleepState } from "../ecs/SleepState.js";
3
+ import { JOINT_WORLD } from "../ecs/Joint.js";
4
+ import { DofMode } from "./DofMode.js";
5
+ import { world_inverse_inertia_apply } from "../inertia/world_inverse_inertia.js";
6
+ import { v3_quat3_apply } from "../../../core/geom/vec3/v3_quat3_apply.js";
7
+ import { quat3_to_matrix3 } from "../../../core/geom/3d/quaternion/quat3_to_matrix3.js";
8
+ import { quat3_multiply } from "../../../core/geom/3d/quaternion/quat3_multiply.js";
9
+
10
+ /**
11
+ * # 6-DOF constraint (joint) solver
12
+ *
13
+ * Runs alongside the contact solver inside the same TGS substep loop: each
14
+ * substep the joints are warm-started and their velocity rows solved, sharing
15
+ * the contact solver's per-substep / warm-start / SPOOK-bias model so the two
16
+ * constraint families converge together (a body touched by both a contact and
17
+ * a joint sees a single coupled Gauss-Seidel sweep across the substep loop).
18
+ *
19
+ * A joint is a configurable 6-DOF constraint ({@link Joint}); each relative
20
+ * degree of freedom is independently locked / free / limited / springy /
21
+ * motorised. This module implements **LOCKED linear** DOFs first — i.e. the
22
+ * ball-socket (point-to-point) joint, which alone covers chains, ropes, and
23
+ * pendulums. Angular locks, limits, springs and motors slot into the same
24
+ * per-DOF loop as they land.
25
+ *
26
+ * Row math is identical in shape to a contact's normal row: for a unit axis
27
+ * `d` and lever arms `rA = anchorA − comA`, `rB = anchorB − comB`,
28
+ * `K = invMA + invMB + (rA×d)·Iw_A·(rA×d) + (rB×d)·Iw_B·(rB×d)`,
29
+ * `vrel = (vA + ωA×rA − vB − ωB×rB) · d`,
30
+ * `λ = −(1/K)·(vrel + bias)`,
31
+ * applied as `±λ·d` at the anchors. Locked DOFs are bilateral (no clamp); the
32
+ * position error is corrected by a SPOOK velocity bias. Geometry (anchors /
33
+ * lever arms / position error) is recomputed from the current pose every
34
+ * substep, so the joint tracks the bodies as they move.
35
+ *
36
+ * @author Alex Goldring
37
+ * @copyright Company Named Limited (c) 2026
38
+ */
39
+
40
+ /**
41
+ * Maximum SPOOK position-correction bias for a joint row, m/s — belt-and-braces
42
+ * against a wildly-violated constraint (e.g. a freshly-teleported body)
43
+ * yanking the solve. Equality joints sit near zero error at steady state, so
44
+ * this only clamps transients.
45
+ * @type {number}
46
+ */
47
+ const MAX_JOINT_BIAS = 4;
48
+
49
+ /**
50
+ * Baumgarte / SPOOK position-correction gain `β`. The per-substep gain is
51
+ * `β / dt_sub`; with `β = 0.2` this matches the contact solver's position
52
+ * stiffness. Joints are bilateral equality constraints, so this only nudges
53
+ * residual position error toward zero (no clamp interplay to destabilise).
54
+ * @type {number}
55
+ */
56
+ const JOINT_BAUMGARTE = 0.2;
57
+
58
+ const scratch_inertia = new Float64Array(3);
59
+ const scratch_anchor = new Float64Array(3);
60
+ /** Frame-A world basis (rows = A's frame axes in world), from quat3_to_matrix3. */
61
+ const scratch_frame_a = new Float64Array(9);
62
+ /** Scratch quaternion for frame composition / relative rotation. */
63
+ const scratch_q = new Float64Array(4);
64
+
65
+ /**
66
+ * Angular effective-mass contribution of one body about a unit world axis:
67
+ * `a · Iw⁻¹ · a`. Zero for non-dynamic / rotation-locked bodies.
68
+ * @returns {number}
69
+ */
70
+ function angular_axis_effective_mass(rb, transform, ax, ay, az) {
71
+ if (rb.kind !== BodyKind.Dynamic) return 0;
72
+ const ii = rb.inverseInertiaLocal;
73
+ if (ii.x === 0 && ii.y === 0 && ii.z === 0) return 0;
74
+ world_inverse_inertia_apply(scratch_inertia, 0, ii, transform.rotation, ax, ay, az);
75
+ return ax * scratch_inertia[0] + ay * scratch_inertia[1] + az * scratch_inertia[2];
76
+ }
77
+
78
+ /**
79
+ * Apply a pure angular impulse `sign·λ·axis`: Δω = Iw⁻¹·(sign·λ·axis).
80
+ * @param {RigidBody} rb @param {Transform} transform
81
+ * @param {number} ax @param {number} ay @param {number} az
82
+ * @param {number} lambda @param {number} sign
83
+ */
84
+ function apply_angular_impulse(rb, transform, ax, ay, az, lambda, sign) {
85
+ if (rb.kind !== BodyKind.Dynamic) return;
86
+ const ii = rb.inverseInertiaLocal;
87
+ if (ii.x === 0 && ii.y === 0 && ii.z === 0) return;
88
+ const L = sign * lambda;
89
+ world_inverse_inertia_apply(scratch_inertia, 0, ii, transform.rotation, ax * L, ay * L, az * L);
90
+ const av = rb.angularVelocity;
91
+ av[0] += scratch_inertia[0];
92
+ av[1] += scratch_inertia[1];
93
+ av[2] += scratch_inertia[2];
94
+ }
95
+
96
+ /**
97
+ * Effective-mass denominator contribution of one body along a unit axis:
98
+ * `invM + (r×d)·Iw⁻¹·(r×d)`. Static / kinematic bodies (invM === 0) contribute
99
+ * nothing.
100
+ *
101
+ * @param {RigidBody} rb
102
+ * @param {Transform} transform
103
+ * @param {number} invM
104
+ * @param {number} rx @param {number} ry @param {number} rz
105
+ * @param {number} dx @param {number} dy @param {number} dz
106
+ * @returns {number}
107
+ */
108
+ function axis_effective_mass(rb, transform, invM, rx, ry, rz, dx, dy, dz) {
109
+ if (invM === 0) return 0;
110
+ let k = invM;
111
+ const ii = rb.inverseInertiaLocal;
112
+ if (ii.x !== 0 || ii.y !== 0 || ii.z !== 0) {
113
+ const cx = ry * dz - rz * dy;
114
+ const cy = rz * dx - rx * dz;
115
+ const cz = rx * dy - ry * dx;
116
+ world_inverse_inertia_apply(scratch_inertia, 0, ii, transform.rotation, cx, cy, cz);
117
+ k += cx * scratch_inertia[0] + cy * scratch_inertia[1] + cz * scratch_inertia[2];
118
+ }
119
+ return k;
120
+ }
121
+
122
+ /**
123
+ * Apply impulse `P` at body-relative offset `r`: Δv = P·invM, Δω = Iw⁻¹·(r×P).
124
+ * Direct typed-array writes (bypass Vector3#set observers — dead weight here).
125
+ *
126
+ * @param {RigidBody} rb @param {Transform} transform @param {number} invM
127
+ * @param {number} rx @param {number} ry @param {number} rz
128
+ * @param {number} Px @param {number} Py @param {number} Pz
129
+ * @param {number} sign +1 / −1
130
+ */
131
+ function apply_impulse(rb, transform, invM, rx, ry, rz, Px, Py, Pz, sign) {
132
+ if (invM === 0) return;
133
+ const sPx = sign * Px, sPy = sign * Py, sPz = sign * Pz;
134
+ const lv = rb.linearVelocity;
135
+ lv[0] += sPx * invM; lv[1] += sPy * invM; lv[2] += sPz * invM;
136
+
137
+ const tx = ry * sPz - rz * sPy;
138
+ const ty = rz * sPx - rx * sPz;
139
+ const tz = rx * sPy - ry * sPx;
140
+ world_inverse_inertia_apply(scratch_inertia, 0, rb.inverseInertiaLocal, transform.rotation, tx, ty, tz);
141
+ const av = rb.angularVelocity;
142
+ av[0] += scratch_inertia[0]; av[1] += scratch_inertia[1]; av[2] += scratch_inertia[2];
143
+ }
144
+
145
+ /**
146
+ * @param {RigidBody} rb
147
+ * @returns {number}
148
+ */
149
+ function inv_mass_of(rb) {
150
+ if (rb.kind !== BodyKind.Dynamic) return 0;
151
+ return rb.mass > 0 ? 1 / rb.mass : 0;
152
+ }
153
+
154
+ /**
155
+ * Solve every joint for one substep: recompute geometry at the current poses,
156
+ * replay the per-substep warm-start, and run `iters` velocity iterations of
157
+ * the locked linear DOFs.
158
+ *
159
+ * Called once per substep from `PhysicsSystem.fixedUpdate`, after the contact
160
+ * solve so the two share the substep / warm-start cadence.
161
+ *
162
+ * @param {Joint[]} joints live joints (sparse array; holes skipped)
163
+ * @param {PhysicsSystem} system reads `__bodies` / `__transforms` / index map
164
+ * @param {number} dt_sub substep size in seconds (the SPOOK gain is derived
165
+ * from it, matching the contact solver's per-substep position stiffness)
166
+ * @param {number} iters velocity iterations
167
+ */
168
+ export function solve_joints(joints, system, dt_sub, iters) {
169
+ const n = joints.length;
170
+ if (n === 0 || dt_sub <= 0) return;
171
+
172
+ const spook_a = JOINT_BAUMGARTE / dt_sub;
173
+
174
+ const storage = system.storage;
175
+
176
+ for (let ji = 0; ji < n; ji++) {
177
+ const joint = joints[ji];
178
+ if (joint === undefined || joint === null) continue;
179
+
180
+ // Generation-checked validity: if A's body was unlinked (and its slot
181
+ // possibly reused by a different body), the stored packed id no longer
182
+ // validates — skip the stale joint rather than attach to the wrong body.
183
+ if (!storage.is_valid(joint._bodyIdA)) continue;
184
+ const idxA = system.__index_of(joint._bodyIdA);
185
+ const rbA = system.__bodies[idxA];
186
+ if (rbA === undefined) continue;
187
+ const trA = system.__transforms[idxA];
188
+
189
+ const to_world = joint._bodyIdB === JOINT_WORLD;
190
+ let rbB = null, trB = null, idxB = -1;
191
+ if (!to_world) {
192
+ if (!storage.is_valid(joint._bodyIdB)) continue;
193
+ idxB = system.__index_of(joint._bodyIdB);
194
+ rbB = system.__bodies[idxB];
195
+ if (rbB === undefined) continue;
196
+ trB = system.__transforms[idxB];
197
+ }
198
+
199
+ // Skip while a participating dynamic body sleeps — its velocity must
200
+ // stay zero until woken. (Island-aware joint sleep is a follow-up; for
201
+ // now jointed bodies that should stay coupled use DisableSleep.)
202
+ if (rbA.sleepState === SleepState.Sleeping) continue;
203
+ if (!to_world && rbB.sleepState === SleepState.Sleeping) continue;
204
+
205
+ // Both bodies static/kinematic → nothing to solve.
206
+ const invMA = inv_mass_of(rbA);
207
+ const invMB = to_world ? 0 : inv_mass_of(rbB);
208
+ if (invMA === 0 && invMB === 0) continue;
209
+
210
+ // World anchor A and its lever arm rA = R_A · localAnchorA.
211
+ const la = joint.localAnchorA;
212
+ v3_quat3_apply(scratch_anchor, 0, la.x, la.y, la.z,
213
+ trA.rotation[0], trA.rotation[1], trA.rotation[2], trA.rotation[3]);
214
+ const rAx = scratch_anchor[0], rAy = scratch_anchor[1], rAz = scratch_anchor[2];
215
+ const anchorAx = trA.position.x + rAx;
216
+ const anchorAy = trA.position.y + rAy;
217
+ const anchorAz = trA.position.z + rAz;
218
+
219
+ // World anchor B + lever arm rB. For a world anchor, localAnchorB is a
220
+ // fixed world point and there is no body B lever.
221
+ let rBx = 0, rBy = 0, rBz = 0;
222
+ let anchorBx, anchorBy, anchorBz;
223
+ const lb = joint.localAnchorB;
224
+ if (to_world) {
225
+ anchorBx = lb.x; anchorBy = lb.y; anchorBz = lb.z;
226
+ } else {
227
+ v3_quat3_apply(scratch_anchor, 0, lb.x, lb.y, lb.z,
228
+ trB.rotation[0], trB.rotation[1], trB.rotation[2], trB.rotation[3]);
229
+ rBx = scratch_anchor[0]; rBy = scratch_anchor[1]; rBz = scratch_anchor[2];
230
+ anchorBx = trB.position.x + rBx;
231
+ anchorBy = trB.position.y + rBy;
232
+ anchorBz = trB.position.z + rBz;
233
+ }
234
+
235
+ // Position error C = anchorA − anchorB (drive to zero on locked axes).
236
+ const Cx = anchorAx - anchorBx;
237
+ const Cy = anchorAy - anchorBy;
238
+ const Cz = anchorAz - anchorBz;
239
+
240
+ const mode = joint.dofMode;
241
+ const imp = joint.dofImpulse;
242
+ const lvA = rbA.linearVelocity, avA = rbA.angularVelocity;
243
+ const lvB = to_world ? null : rbB.linearVelocity;
244
+ const avB = to_world ? null : rbB.angularVelocity;
245
+
246
+ // === Frame A world axes — shared by the linear AND angular rows ===
247
+ // Every DOF is expressed in the joint's frame on body A. A locked
248
+ // linear DOF pins the anchor offset along a frame axis (all three →
249
+ // ball-socket); a FREE linear DOF is a prismatic slide. A locked
250
+ // angular DOF pins relative rotation about a frame axis; a FREE one is
251
+ // a hinge axis. Frame A = body-A rotation ⊗ localBasisA; the rows of
252
+ // its matrix are the frame axes in world (see quat3_to_matrix3).
253
+ const linLockX = mode[0] === DofMode.LOCKED;
254
+ const linLockY = mode[1] === DofMode.LOCKED;
255
+ const linLockZ = mode[2] === DofMode.LOCKED;
256
+ const angLockX = mode[3] === DofMode.LOCKED;
257
+ const angLockY = mode[4] === DofMode.LOCKED;
258
+ const angLockZ = mode[5] === DofMode.LOCKED;
259
+ if (!(linLockX || linLockY || linLockZ || angLockX || angLockY || angLockZ)) continue;
260
+
261
+ const lba = joint.localBasisA;
262
+ quat3_multiply(scratch_q, 0, trA.rotation[0], trA.rotation[1], trA.rotation[2], trA.rotation[3], lba[0], lba[1], lba[2], lba[3]);
263
+ const qAx = scratch_q[0], qAy = scratch_q[1], qAz = scratch_q[2], qAw = scratch_q[3];
264
+ quat3_to_matrix3(scratch_frame_a, 0, qAx, qAy, qAz, qAw);
265
+ const aXx = scratch_frame_a[0], aXy = scratch_frame_a[1], aXz = scratch_frame_a[2];
266
+ const aYx = scratch_frame_a[3], aYy = scratch_frame_a[4], aYz = scratch_frame_a[5];
267
+ const aZx = scratch_frame_a[6], aZy = scratch_frame_a[7], aZz = scratch_frame_a[8];
268
+
269
+ // --- Linear rows: effective mass + SPOOK bias along each locked frame
270
+ // axis. Error along an axis is `C · axis` (signed anchor offset).
271
+ // Convention is A−B (relative anchor velocity vA−vB, impulse +to A
272
+ // / −to B), matching contacts. ---
273
+ let m_eff_lx = 0, m_eff_ly = 0, m_eff_lz = 0;
274
+ let biasLx = 0, biasLy = 0, biasLz = 0;
275
+ if (linLockX) {
276
+ const k = axis_effective_mass(rbA, trA, invMA, rAx, rAy, rAz, aXx, aXy, aXz)
277
+ + (to_world ? 0 : axis_effective_mass(rbB, trB, invMB, rBx, rBy, rBz, aXx, aXy, aXz));
278
+ m_eff_lx = k > 0 ? 1 / k : 0;
279
+ let b = spook_a * (Cx * aXx + Cy * aXy + Cz * aXz);
280
+ if (b > MAX_JOINT_BIAS) b = MAX_JOINT_BIAS; else if (b < -MAX_JOINT_BIAS) b = -MAX_JOINT_BIAS;
281
+ biasLx = b;
282
+ }
283
+ if (linLockY) {
284
+ const k = axis_effective_mass(rbA, trA, invMA, rAx, rAy, rAz, aYx, aYy, aYz)
285
+ + (to_world ? 0 : axis_effective_mass(rbB, trB, invMB, rBx, rBy, rBz, aYx, aYy, aYz));
286
+ m_eff_ly = k > 0 ? 1 / k : 0;
287
+ let b = spook_a * (Cx * aYx + Cy * aYy + Cz * aYz);
288
+ if (b > MAX_JOINT_BIAS) b = MAX_JOINT_BIAS; else if (b < -MAX_JOINT_BIAS) b = -MAX_JOINT_BIAS;
289
+ biasLy = b;
290
+ }
291
+ if (linLockZ) {
292
+ const k = axis_effective_mass(rbA, trA, invMA, rAx, rAy, rAz, aZx, aZy, aZz)
293
+ + (to_world ? 0 : axis_effective_mass(rbB, trB, invMB, rBx, rBy, rBz, aZx, aZy, aZz));
294
+ m_eff_lz = k > 0 ? 1 / k : 0;
295
+ let b = spook_a * (Cx * aZx + Cy * aZy + Cz * aZz);
296
+ if (b > MAX_JOINT_BIAS) b = MAX_JOINT_BIAS; else if (b < -MAX_JOINT_BIAS) b = -MAX_JOINT_BIAS;
297
+ biasLz = b;
298
+ }
299
+ const doLinX = linLockX && m_eff_lx !== 0;
300
+ const doLinY = linLockY && m_eff_ly !== 0;
301
+ const doLinZ = linLockZ && m_eff_lz !== 0;
302
+
303
+ // --- Angular rows: relative-rotation error + effective mass about each
304
+ // locked frame axis. Convention is B−A (relative angular velocity
305
+ // ωB−ωA, impulse +to B / −to A). ---
306
+ let m_eff_ax = 0, m_eff_ay = 0, m_eff_az = 0;
307
+ let biasAx = 0, biasAy = 0, biasAz = 0;
308
+ let lockAX = false, lockAY = false, lockAZ = false;
309
+ if (angLockX || angLockY || angLockZ) {
310
+ // Frame B world rotation (identity for a world anchor → locks A's
311
+ // frame to world axes).
312
+ let qBx = 0, qBy = 0, qBz = 0, qBw = 1;
313
+ if (!to_world) {
314
+ const lbb = joint.localBasisB;
315
+ quat3_multiply(scratch_q, 0, trB.rotation[0], trB.rotation[1], trB.rotation[2], trB.rotation[3], lbb[0], lbb[1], lbb[2], lbb[3]);
316
+ qBx = scratch_q[0]; qBy = scratch_q[1]; qBz = scratch_q[2]; qBw = scratch_q[3];
317
+ }
318
+ // qD = conj(qA) ⊗ qB; small-angle rotation vector (shortest path via
319
+ // sign(w)) is the per-axis error in frame-A-local coords — pairs
320
+ // with the world frame axes above (axis i ↔ error component i).
321
+ quat3_multiply(scratch_q, 0, -qAx, -qAy, -qAz, qAw, qBx, qBy, qBz, qBw);
322
+ const es = scratch_q[3] < 0 ? -2 : 2;
323
+ const eX = es * scratch_q[0];
324
+ const eY = es * scratch_q[1];
325
+ const eZ = es * scratch_q[2];
326
+
327
+ if (angLockX) {
328
+ const k = angular_axis_effective_mass(rbA, trA, aXx, aXy, aXz)
329
+ + (to_world ? 0 : angular_axis_effective_mass(rbB, trB, aXx, aXy, aXz));
330
+ m_eff_ax = k > 0 ? 1 / k : 0;
331
+ let b = spook_a * eX;
332
+ if (b > MAX_JOINT_BIAS) b = MAX_JOINT_BIAS; else if (b < -MAX_JOINT_BIAS) b = -MAX_JOINT_BIAS;
333
+ biasAx = b;
334
+ lockAX = m_eff_ax !== 0;
335
+ }
336
+ if (angLockY) {
337
+ const k = angular_axis_effective_mass(rbA, trA, aYx, aYy, aYz)
338
+ + (to_world ? 0 : angular_axis_effective_mass(rbB, trB, aYx, aYy, aYz));
339
+ m_eff_ay = k > 0 ? 1 / k : 0;
340
+ let b = spook_a * eY;
341
+ if (b > MAX_JOINT_BIAS) b = MAX_JOINT_BIAS; else if (b < -MAX_JOINT_BIAS) b = -MAX_JOINT_BIAS;
342
+ biasAy = b;
343
+ lockAY = m_eff_ay !== 0;
344
+ }
345
+ if (angLockZ) {
346
+ const k = angular_axis_effective_mass(rbA, trA, aZx, aZy, aZz)
347
+ + (to_world ? 0 : angular_axis_effective_mass(rbB, trB, aZx, aZy, aZz));
348
+ m_eff_az = k > 0 ? 1 / k : 0;
349
+ let b = spook_a * eZ;
350
+ if (b > MAX_JOINT_BIAS) b = MAX_JOINT_BIAS; else if (b < -MAX_JOINT_BIAS) b = -MAX_JOINT_BIAS;
351
+ biasAz = b;
352
+ lockAZ = m_eff_az !== 0;
353
+ }
354
+ }
355
+
356
+ // --- Per-substep warm-start (frame axes): linear impulse `Σ imp[i]·aᵢ`
357
+ // applied at the anchors, angular impulse `Σ imp[3+i]·aᵢ` about the
358
+ // frame axes (+to B / −to A). ---
359
+ {
360
+ const Px = (doLinX ? imp[0] : 0) * aXx + (doLinY ? imp[1] : 0) * aYx + (doLinZ ? imp[2] : 0) * aZx;
361
+ const Py = (doLinX ? imp[0] : 0) * aXy + (doLinY ? imp[1] : 0) * aYy + (doLinZ ? imp[2] : 0) * aZy;
362
+ const Pz = (doLinX ? imp[0] : 0) * aXz + (doLinY ? imp[1] : 0) * aYz + (doLinZ ? imp[2] : 0) * aZz;
363
+ if (Px !== 0 || Py !== 0 || Pz !== 0) {
364
+ apply_impulse(rbA, trA, invMA, rAx, rAy, rAz, Px, Py, Pz, +1);
365
+ if (!to_world) apply_impulse(rbB, trB, invMB, rBx, rBy, rBz, Px, Py, Pz, -1);
366
+ }
367
+ const Lx = (lockAX ? imp[3] : 0) * aXx + (lockAY ? imp[4] : 0) * aYx + (lockAZ ? imp[5] : 0) * aZx;
368
+ const Ly = (lockAX ? imp[3] : 0) * aXy + (lockAY ? imp[4] : 0) * aYy + (lockAZ ? imp[5] : 0) * aZy;
369
+ const Lz = (lockAX ? imp[3] : 0) * aXz + (lockAY ? imp[4] : 0) * aYz + (lockAZ ? imp[5] : 0) * aZz;
370
+ if (Lx !== 0 || Ly !== 0 || Lz !== 0) {
371
+ if (!to_world) apply_angular_impulse(rbB, trB, Lx, Ly, Lz, 1, +1);
372
+ apply_angular_impulse(rbA, trA, Lx, Ly, Lz, 1, -1);
373
+ }
374
+ }
375
+
376
+ for (let it = 0; it < iters; it++) {
377
+ // Linear rows (frame axes). The relative anchor velocity is
378
+ // recomputed before each row so successive rows see the prior
379
+ // impulses (Gauss-Seidel coupling).
380
+ if (doLinX) {
381
+ const rvx = (lvA[0] + avA[1] * rAz - avA[2] * rAy) - (to_world ? 0 : (lvB[0] + avB[1] * rBz - avB[2] * rBy));
382
+ const rvy = (lvA[1] + avA[2] * rAx - avA[0] * rAz) - (to_world ? 0 : (lvB[1] + avB[2] * rBx - avB[0] * rBz));
383
+ const rvz = (lvA[2] + avA[0] * rAy - avA[1] * rAx) - (to_world ? 0 : (lvB[2] + avB[0] * rBy - avB[1] * rBx));
384
+ const lambda = -m_eff_lx * ((rvx * aXx + rvy * aXy + rvz * aXz) + biasLx);
385
+ imp[0] += lambda;
386
+ apply_impulse(rbA, trA, invMA, rAx, rAy, rAz, lambda * aXx, lambda * aXy, lambda * aXz, +1);
387
+ if (!to_world) apply_impulse(rbB, trB, invMB, rBx, rBy, rBz, lambda * aXx, lambda * aXy, lambda * aXz, -1);
388
+ }
389
+ if (doLinY) {
390
+ const rvx = (lvA[0] + avA[1] * rAz - avA[2] * rAy) - (to_world ? 0 : (lvB[0] + avB[1] * rBz - avB[2] * rBy));
391
+ const rvy = (lvA[1] + avA[2] * rAx - avA[0] * rAz) - (to_world ? 0 : (lvB[1] + avB[2] * rBx - avB[0] * rBz));
392
+ const rvz = (lvA[2] + avA[0] * rAy - avA[1] * rAx) - (to_world ? 0 : (lvB[2] + avB[0] * rBy - avB[1] * rBx));
393
+ const lambda = -m_eff_ly * ((rvx * aYx + rvy * aYy + rvz * aYz) + biasLy);
394
+ imp[1] += lambda;
395
+ apply_impulse(rbA, trA, invMA, rAx, rAy, rAz, lambda * aYx, lambda * aYy, lambda * aYz, +1);
396
+ if (!to_world) apply_impulse(rbB, trB, invMB, rBx, rBy, rBz, lambda * aYx, lambda * aYy, lambda * aYz, -1);
397
+ }
398
+ if (doLinZ) {
399
+ const rvx = (lvA[0] + avA[1] * rAz - avA[2] * rAy) - (to_world ? 0 : (lvB[0] + avB[1] * rBz - avB[2] * rBy));
400
+ const rvy = (lvA[1] + avA[2] * rAx - avA[0] * rAz) - (to_world ? 0 : (lvB[1] + avB[2] * rBx - avB[0] * rBz));
401
+ const rvz = (lvA[2] + avA[0] * rAy - avA[1] * rAx) - (to_world ? 0 : (lvB[2] + avB[0] * rBy - avB[1] * rBx));
402
+ const lambda = -m_eff_lz * ((rvx * aZx + rvy * aZy + rvz * aZz) + biasLz);
403
+ imp[2] += lambda;
404
+ apply_impulse(rbA, trA, invMA, rAx, rAy, rAz, lambda * aZx, lambda * aZy, lambda * aZz, +1);
405
+ if (!to_world) apply_impulse(rbB, trB, invMB, rBx, rBy, rBz, lambda * aZx, lambda * aZy, lambda * aZz, -1);
406
+ }
407
+
408
+ // Angular rows (frame axes): drive ωB−ωA about each locked axis to
409
+ // zero with the SPOOK bias.
410
+ if (lockAX) {
411
+ let wrel = -(avA[0] * aXx + avA[1] * aXy + avA[2] * aXz);
412
+ if (!to_world) wrel += avB[0] * aXx + avB[1] * aXy + avB[2] * aXz;
413
+ const lambda = -m_eff_ax * (wrel + biasAx);
414
+ imp[3] += lambda;
415
+ if (!to_world) apply_angular_impulse(rbB, trB, aXx, aXy, aXz, lambda, +1);
416
+ apply_angular_impulse(rbA, trA, aXx, aXy, aXz, lambda, -1);
417
+ }
418
+ if (lockAY) {
419
+ let wrel = -(avA[0] * aYx + avA[1] * aYy + avA[2] * aYz);
420
+ if (!to_world) wrel += avB[0] * aYx + avB[1] * aYy + avB[2] * aYz;
421
+ const lambda = -m_eff_ay * (wrel + biasAy);
422
+ imp[4] += lambda;
423
+ if (!to_world) apply_angular_impulse(rbB, trB, aYx, aYy, aYz, lambda, +1);
424
+ apply_angular_impulse(rbA, trA, aYx, aYy, aYz, lambda, -1);
425
+ }
426
+ if (lockAZ) {
427
+ let wrel = -(avA[0] * aZx + avA[1] * aZy + avA[2] * aZz);
428
+ if (!to_world) wrel += avB[0] * aZx + avB[1] * aZy + avB[2] * aZz;
429
+ const lambda = -m_eff_az * (wrel + biasAz);
430
+ imp[5] += lambda;
431
+ if (!to_world) apply_angular_impulse(rbB, trB, aZx, aZy, aZz, lambda, +1);
432
+ apply_angular_impulse(rbA, trA, aZx, aZy, aZz, lambda, -1);
433
+ }
434
+ }
435
+ }
436
+ }
@@ -9,21 +9,30 @@ export const MAX_CONTACTS_PER_MANIFOLD: number;
9
9
  * Per-contact field stride in the data Float64Array.
10
10
  *
11
11
  * Layout per contact (all world-space):
12
- * 0..2 : world_a (contact point on body A's surface)
13
- * 3..5 : world_b (contact point on body B's surface)
14
- * 6..8 : normal_w (from B toward A)
15
- * 9 : depth (positive = penetration, negative = speculative gap)
16
- * 10 : j_n (accumulated normal impulse, warm-start)
17
- * 11 : j_t1 (accumulated tangent impulse, axis 1)
18
- * 12 : j_t2 (accumulated tangent impulse, axis 2)
12
+ * 0..2 : world_a (contact point on body A's surface)
13
+ * 3..5 : world_b (contact point on body B's surface)
14
+ * 6..8 : normal_w (from B toward A)
15
+ * 9 : depth (positive = penetration, negative = speculative gap)
16
+ * 10 : j_n (accumulated normal impulse, warm-start)
17
+ * 11 : j_t1 (accumulated tangent impulse, axis 1)
18
+ * 12 : j_t2 (accumulated tangent impulse, axis 2)
19
+ * 13 : feature_id (uint32 packed as f64 — stable cross-frame ID of the
20
+ * geometric feature pair that produced this contact;
21
+ * 0 means "no info, fall back to position matching")
19
22
  *
20
23
  * Solver uses `(world_a + world_b) * 0.5` as the application point; storing
21
- * both surface points keeps the door open for richer warm-start matching
22
- * across frames in a follow-up slice.
24
+ * both surface points enables the per-frame warm-start matcher in the
25
+ * narrowphase to compare contact-point positions when feature_id is 0.
23
26
  *
24
27
  * @type {number}
25
28
  */
26
29
  export const CONTACT_STRIDE: number;
30
+ /**
31
+ * Per-contact feature_id offset within {@link CONTACT_STRIDE}.
32
+ * Exposed for the narrowphase matcher; solver code does not read this.
33
+ * @type {number}
34
+ */
35
+ export const CONTACT_FEATURE_ID_OFFSET: number;
27
36
  /**
28
37
  * Per-slot Float64 stride: room for {@link MAX_CONTACTS_PER_MANIFOLD} contacts.
29
38
  * @type {number}
@@ -65,6 +74,7 @@ export class ManifoldStore {
65
74
  __high_water: number;
66
75
  __data: Float64Array;
67
76
  __meta: Uint32Array;
77
+ __slot_axis: Float64Array;
68
78
  __pair_index: PairUint32Map;
69
79
  __live_slots: Uint32Array;
70
80
  __live_pos: Int32Array;
@@ -137,10 +147,31 @@ export class ManifoldStore {
137
147
  * @param {number} slot
138
148
  */
139
149
  clear_contacts(slot: number): void;
150
+ /**
151
+ * Reset the contact count to zero so the next `set_contact(slot, 0, …)`
152
+ * lands at index 0, but DO NOT zero the data slab. Warm-start impulses
153
+ * (`j_n`, `j_t1`, `j_t2`) at each contact slot survive — the subsequent
154
+ * `set_contact` calls only overwrite the first 10 floats (position,
155
+ * normal, depth) per contact.
156
+ *
157
+ * Use this when narrowphase determines the pair still has overlap and
158
+ * is about to refill the slot with the new frame's contacts. Use
159
+ * {@link clear_contacts} instead when the pair has genuinely separated
160
+ * and the cached impulses should be evicted.
161
+ *
162
+ * @param {number} slot
163
+ */
164
+ begin_refill(slot: number): void;
140
165
  /**
141
166
  * Write a contact point into a slot. Increments contact_count if `idx`
142
167
  * exceeds the current count.
143
168
  *
169
+ * Writes geometry (offsets 0..9) and the feature_id at offset 13.
170
+ * Deliberately does NOT touch the warm-start impulse fields (offsets
171
+ * 10..12) — the narrowphase match-and-merge pass uses this to refill
172
+ * a slot while preserving the cached impulses that the solver's
173
+ * warm-start needs.
174
+ *
144
175
  * @param {number} slot
145
176
  * @param {number} idx contact index in `[0, MAX_CONTACTS_PER_MANIFOLD)`
146
177
  * @param {number} lax
@@ -153,8 +184,35 @@ export class ManifoldStore {
153
184
  * @param {number} ny
154
185
  * @param {number} nz
155
186
  * @param {number} depth
187
+ * @param {number} feature_id stable cross-frame identifier of the
188
+ * geometric feature pair that produced this contact; 0 means
189
+ * "no info, fall back to position matching"
156
190
  */
157
- set_contact(slot: number, idx: number, lax: number, lay: number, laz: number, lbx: number, lby: number, lbz: number, nx: number, ny: number, nz: number, depth: number): void;
191
+ set_contact(slot: number, idx: number, lax: number, lay: number, laz: number, lbx: number, lby: number, lbz: number, nx: number, ny: number, nz: number, depth: number, feature_id?: number): void;
192
+ /**
193
+ * Zero the warm-start impulse fields (j_n, j_t1, j_t2) for one contact
194
+ * index inside a slot. Called by the narrowphase match-and-merge pass
195
+ * after writing a candidate that did NOT match any prior-frame contact:
196
+ * the geometry at this index is "new" and inheriting impulses from
197
+ * whatever happened to be cached at this index last frame would
198
+ * destabilise the solver.
199
+ *
200
+ * Distinct from {@link clear_contacts} (which wipes the entire slot's
201
+ * data slab including geometry) — `clear_impulses` only touches the
202
+ * three impulse fields at one contact index, leaving the geometric
203
+ * fields and feature_id intact.
204
+ *
205
+ * @param {number} slot
206
+ * @param {number} idx
207
+ */
208
+ clear_impulses(slot: number, idx: number): void;
209
+ /**
210
+ * Read the feature_id stored at one contact within a slot.
211
+ * @param {number} slot
212
+ * @param {number} idx
213
+ * @returns {number}
214
+ */
215
+ feature_id_of(slot: number, idx: number): number;
158
216
  /**
159
217
  * Read a contact point. Writes 10 floats: lax, lay, laz, lbx, lby, lbz, nx, ny, nz, depth.
160
218
  * @param {number} slot
@@ -184,6 +242,21 @@ export class ManifoldStore {
184
242
  * @returns {Float64Array}
185
243
  */
186
244
  get data_buffer(): Float64Array;
245
+ /**
246
+ * Per-slot cached GJK separating axis buffer. Hot-path GJK callers
247
+ * pass this together with {@link slot_axis_offset} to
248
+ * `gjk_with_axis(...)` so the cached axis seeds the next iteration.
249
+ * Buffer identity is stable until grow.
250
+ * @returns {Float64Array}
251
+ */
252
+ get slot_axis_buffer(): Float64Array;
253
+ /**
254
+ * Float offset into {@link slot_axis_buffer} where this slot's
255
+ * cached axis lives (3 floats: x, y, z).
256
+ * @param {number} slot
257
+ * @returns {number}
258
+ */
259
+ slot_axis_offset(slot: number): number;
187
260
  /**
188
261
  * Word offset into {@link data_buffer} where the first contact of `slot`
189
262
  * begins. Subsequent contacts within the slot are at
@@ -1 +1 @@
1
- {"version":3,"file":"ManifoldStore.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/contact/ManifoldStore.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wCAFU,MAAM,CAE2B;AAE3C;;;;;;;;;;;;;;;;;GAiBG;AACH,6BAFU,MAAM,CAEiB;AAEjC;;;GAGG;AACH,+BAFU,MAAM,CAE2D;AAyB3E;;;GAGG;AACH,mCAFU,MAAM,CAEuB;AAIvC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IAEI;;OAEG;IACH,+BAFW,MAAM,EAqBhB;IAjBG,mBAA+C;IAC/C,qBAAqB;IAErB,qBAAkE;IAClE,oBAAiE;IAEjE,4BAA0D;IAG1D,0BAAoD;IACpD,uBAAiD;IAEjD,qBAAqB;IAGrB,yBAAmD;IACnD,qBAAqB;IAGzB;;OAEG;IACH,oBAEC;IAED;;OAEG;IACH,uBAEC;IAED;;;;;;;OAOG;IACH,UAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAKlB;IAED;;;;;;;;OAQG;IACH,aAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAwBlB;IAED;;;OAGG;IACH,YAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,YAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,oBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,iBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,uBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;;;OAKG;IACH,WAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;OAKG;IACH,qBAFW,MAAM,QAQhB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,kBAbW,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,SACN,MAAM,QAqBhB;IAED;;;;;;OAMG;IACH,kBALW,MAAM,OACN,MAAM,OACN,MAAM,EAAE,GAAC,YAAY,cACrB,MAAM,QAOhB;IAED;;;;;;OAMG;IACH,uBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAIlB;IAED,iEAAiE;IACjE,uBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED,iEAAiE;IACjE,uBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED,iEAAiE;IACjE,oBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED;;;;;OAKG;IACH,gCAEC;IAED;;;;;;OAMG;IACH,uBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,sBA6BC;IAED;;;OAGG;IACH,wBAcC;IAED;;;OAGG;IACH,uBAkBC;IAED;;OAEG;IACH,eA8BC;IAED;;;OAGG;IACH,oBAaC;IAED;;;OAGG;IACH,mBAqBC;CACJ;8BAjfqD,2CAA2C"}
1
+ {"version":3,"file":"ManifoldStore.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/contact/ManifoldStore.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wCAFU,MAAM,CAE2B;AAE3C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,6BAFU,MAAM,CAEiB;AAEjC;;;;GAIG;AACH,wCAFU,MAAM,CAE4B;AAE5C;;;GAGG;AACH,+BAFU,MAAM,CAE2D;AAyB3E;;;GAGG;AACH,mCAFU,MAAM,CAEuB;AAIvC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IAEI;;OAEG;IACH,+BAFW,MAAM,EA6BhB;IAzBG,mBAA+C;IAC/C,qBAAqB;IAErB,qBAAkE;IAClE,oBAAiE;IAQjE,0BAAwD;IAExD,4BAA0D;IAG1D,0BAAoD;IACpD,uBAAiD;IAEjD,qBAAqB;IAGrB,yBAAmD;IACnD,qBAAqB;IAGzB;;OAEG;IACH,oBAEC;IAED;;OAEG;IACH,uBAEC;IAED;;;;;;;OAOG;IACH,UAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAKlB;IAED;;;;;;;;OAQG;IACH,aAJW,MAAM,OACN,MAAM,GACJ,MAAM,CA+BlB;IAED;;;OAGG;IACH,YAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,YAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,oBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,iBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,uBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;;;OAKG;IACH,WAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;OAKG;IACH,qBAFW,MAAM,QAQhB;IAED;;;;;;;;;;;;;OAaG;IACH,mBAFW,MAAM,QAKhB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,kBAhBW,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,SACN,MAAM,eACN,MAAM,QAwBhB;IAED;;;;;;;;;;;;;;;OAeG;IACH,qBAHW,MAAM,OACN,MAAM,QAOhB;IAED;;;;;OAKG;IACH,oBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;OAMG;IACH,kBALW,MAAM,OACN,MAAM,OACN,MAAM,EAAE,GAAC,YAAY,cACrB,MAAM,QAOhB;IAED;;;;;;OAMG;IACH,uBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAIlB;IAED,iEAAiE;IACjE,uBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED,iEAAiE;IACjE,uBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED,iEAAiE;IACjE,oBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED;;;;;OAKG;IACH,gCAEC;IAED;;;;;;OAMG;IACH,qCAEC;IAED;;;;;OAKG;IACH,uBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;OAMG;IACH,uBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,sBA6BC;IAED;;;OAGG;IACH,wBAcC;IAED;;;OAGG;IACH,uBAkBC;IAED;;OAEG;IACH,eA+BC;IAED;;;OAGG;IACH,oBAaC;IAED;;;OAGG;IACH,mBAqBC;CACJ;8BA9lBqD,2CAA2C"}