@woosh/meep-engine 2.139.0 → 2.140.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 (172) 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_to_matrix3.d.ts +54 -0
  9. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts.map +1 -0
  10. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -0
  11. package/src/core/geom/3d/shape/AbstractShape3D.d.ts +24 -2
  12. package/src/core/geom/3d/shape/AbstractShape3D.d.ts.map +1 -1
  13. package/src/core/geom/3d/shape/AbstractShape3D.js +24 -1
  14. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +148 -0
  15. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -0
  16. package/src/core/geom/3d/shape/HeightMapShape3D.js +451 -0
  17. package/src/core/geom/3d/shape/MeshShape3D.d.ts +210 -0
  18. package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -0
  19. package/src/core/geom/3d/shape/MeshShape3D.js +593 -0
  20. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  21. package/src/core/geom/3d/shape/TransformedShape3D.js +46 -2
  22. package/src/core/geom/3d/shape/Triangle3D.d.ts +95 -0
  23. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -0
  24. package/src/core/geom/3d/shape/Triangle3D.js +318 -0
  25. package/src/core/geom/3d/shape/UnionShape3D.js +13 -0
  26. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts +30 -0
  27. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts.map +1 -0
  28. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +64 -0
  29. package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.js +9 -11
  30. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts +28 -0
  31. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts.map +1 -0
  32. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -0
  33. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.d.ts.map +1 -1
  34. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +40 -18
  35. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts +9 -5
  36. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts.map +1 -1
  37. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +38 -10
  38. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts +14 -5
  39. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts.map +1 -1
  40. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +47 -5
  41. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts +19 -0
  42. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts.map +1 -1
  43. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +75 -13
  44. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts +2 -2
  45. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts.map +1 -1
  46. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +1 -1
  47. package/src/core/geom/vec3/v3_dot_array_array.d.ts +3 -3
  48. package/src/core/geom/vec3/v3_dot_array_array.d.ts.map +1 -1
  49. package/src/core/geom/vec3/v3_dot_array_array.js +2 -2
  50. package/src/core/geom/vec3/v3_negate_array.d.ts +3 -3
  51. package/src/core/geom/vec3/v3_negate_array.d.ts.map +1 -1
  52. package/src/core/geom/vec3/v3_negate_array.js +2 -2
  53. package/src/core/geom/vec3/v3_quat3_apply.d.ts +29 -0
  54. package/src/core/geom/vec3/v3_quat3_apply.d.ts.map +1 -0
  55. package/src/core/geom/vec3/v3_quat3_apply.js +39 -0
  56. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts +30 -0
  57. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts.map +1 -0
  58. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +41 -0
  59. package/src/core/geom/vec3/v3_triple_cross_product.d.ts +32 -0
  60. package/src/core/geom/vec3/v3_triple_cross_product.d.ts.map +1 -0
  61. package/src/core/geom/vec3/v3_triple_cross_product.js +45 -0
  62. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +16 -3
  63. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  64. package/src/engine/control/first-person/FirstPersonPlayerController.js +211 -211
  65. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +72 -8
  66. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  67. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +37 -5
  68. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +101 -3
  69. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  70. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1789 -1416
  71. package/src/engine/control/first-person/TODO.md +173 -127
  72. package/src/engine/control/first-person/abilities/Slide.d.ts.map +1 -1
  73. package/src/engine/control/first-person/abilities/Slide.js +9 -1
  74. package/src/engine/control/first-person/prototype_first_person_controller.js +88 -2
  75. package/src/engine/control/first-person/test/buildTestPlayer.d.ts.map +1 -1
  76. package/src/engine/control/first-person/test/buildTestPlayer.js +9 -1
  77. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts +42 -0
  78. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts.map +1 -0
  79. package/src/engine/graphics/geometry/CapsuleGeometry.js +171 -0
  80. package/src/engine/physics/BULLET_REVIEW.md +945 -0
  81. package/src/engine/physics/CANNON_REVIEW.md +1300 -0
  82. package/src/engine/physics/JOLT_REVIEW.md +913 -0
  83. package/src/engine/physics/PLAN.md +461 -236
  84. package/src/engine/physics/RAPIER_REVIEW.md +934 -0
  85. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -0
  86. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +2 -2
  87. package/src/engine/physics/contact/ManifoldStore.d.ts +83 -10
  88. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  89. package/src/engine/physics/contact/ManifoldStore.js +608 -499
  90. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts +2 -2
  91. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts.map +1 -1
  92. package/src/engine/physics/ecs/PhysicsSystem.d.ts +128 -20
  93. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  94. package/src/engine/physics/ecs/PhysicsSystem.js +1301 -1159
  95. package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
  96. package/src/engine/physics/fluid/FluidSimulator.js +2 -1
  97. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +28 -6
  98. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -1
  99. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +39 -17
  100. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +6 -6
  101. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +1 -1
  102. package/src/engine/physics/gjk/expanding_polytope_algorithm.js +68 -22
  103. package/src/engine/physics/gjk/gjk.d.ts +28 -2
  104. package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
  105. package/src/engine/physics/gjk/gjk.js +421 -378
  106. package/src/engine/physics/gjk/minkowski_support.d.ts +37 -0
  107. package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -0
  108. package/src/engine/physics/gjk/minkowski_support.js +75 -0
  109. package/src/engine/physics/gjk/mpr.d.ts +56 -0
  110. package/src/engine/physics/gjk/mpr.d.ts.map +1 -0
  111. package/src/engine/physics/gjk/mpr.js +344 -0
  112. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +20 -5
  113. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  114. package/src/engine/physics/inertia/world_inverse_inertia.js +36 -38
  115. package/src/engine/physics/integration/integrate_position.d.ts +25 -7
  116. package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
  117. package/src/engine/physics/integration/integrate_position.js +43 -12
  118. package/src/engine/physics/integration/integrate_velocity.d.ts +30 -0
  119. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
  120. package/src/engine/physics/integration/integrate_velocity.js +82 -1
  121. package/src/engine/physics/narrowphase/PosedShape.d.ts +0 -8
  122. package/src/engine/physics/narrowphase/PosedShape.d.ts.map +1 -1
  123. package/src/engine/physics/narrowphase/PosedShape.js +28 -30
  124. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  125. package/src/engine/physics/narrowphase/box_box_manifold.js +113 -17
  126. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts +30 -0
  127. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -0
  128. package/src/engine/physics/narrowphase/box_triangle_contact.js +811 -0
  129. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  130. package/src/engine/physics/narrowphase/capsule_contacts.js +10 -56
  131. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts +71 -0
  132. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts.map +1 -0
  133. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +375 -0
  134. package/src/engine/physics/narrowphase/compute_penetration.d.ts +91 -0
  135. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -0
  136. package/src/engine/physics/narrowphase/compute_penetration.js +396 -0
  137. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts +35 -0
  138. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts.map +1 -0
  139. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.js +80 -0
  140. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts +31 -0
  141. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts.map +1 -0
  142. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -0
  143. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +42 -0
  144. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -0
  145. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +204 -0
  146. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts +42 -0
  147. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -0
  148. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +94 -0
  149. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts +37 -0
  150. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts.map +1 -0
  151. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -0
  152. package/src/engine/physics/narrowphase/narrowphase_step.d.ts +8 -2
  153. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  154. package/src/engine/physics/narrowphase/narrowphase_step.js +1422 -382
  155. package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
  156. package/src/engine/physics/narrowphase/sphere_box_contact.js +16 -23
  157. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts +48 -0
  158. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts.map +1 -0
  159. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +143 -0
  160. package/src/engine/physics/queries/overlap_shape.d.ts +51 -0
  161. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -0
  162. package/src/engine/physics/queries/overlap_shape.js +183 -0
  163. package/src/engine/physics/queries/shape_cast.d.ts +56 -0
  164. package/src/engine/physics/queries/shape_cast.d.ts.map +1 -0
  165. package/src/engine/physics/queries/shape_cast.js +387 -0
  166. package/src/engine/physics/solver/solve_contacts.d.ts +116 -30
  167. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  168. package/src/engine/physics/solver/solve_contacts.js +641 -223
  169. package/src/engine/physics/broadphase/aabb_transform_oriented.d.ts.map +0 -1
  170. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts +0 -20
  171. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts.map +0 -1
  172. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.js +0 -83
@@ -0,0 +1,387 @@
1
+ import { bvh_query_user_data_overlaps_aabb } from "../../../core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js";
2
+ import { returnTrue } from "../../../core/function/returnTrue.js";
3
+ import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
4
+ import { body_id_index } from "../body/BodyStorage.js";
5
+ import { gjk } from "../gjk/gjk.js";
6
+ import { mpr } from "../gjk/mpr.js";
7
+ import { PosedShape } from "../narrowphase/PosedShape.js";
8
+
9
+ /**
10
+ * Bisection halts when the search interval is below this fraction of
11
+ * the sweep length. With `MAX_BISECTION = 32` and a unit-length sweep
12
+ * the achieved precision is ≈ 1 / 2³² (well below any practical
13
+ * world-scale tolerance), but we cap on relative tolerance so a
14
+ * 10 m sweep doesn't insist on sub-micron precision.
15
+ * @type {number}
16
+ */
17
+ const TOI_TOLERANCE = 1e-4;
18
+
19
+ /**
20
+ * Maximum bisection iterations. `log2(1 / TOI_TOLERANCE)` is enough
21
+ * for any practical sweep; this is the hard upper bound to prevent
22
+ * pathological inputs (NaN ray, etc.) from hanging the query.
23
+ * @type {number}
24
+ */
25
+ const MAX_BISECTION = 32;
26
+
27
+ /**
28
+ * Coarse linear-search resolution for finding *any* overlap moment
29
+ * along the per-candidate AABB-overlap interval (NOT the full sweep —
30
+ * see `aabb_overlap_interval` for the analytical narrowing that runs
31
+ * upstream). 32 substeps over the AABB-overlap window is dense enough
32
+ * for any sweep where the shape's smallest extent is on the order of
33
+ * its AABB's extent (true for spheres / boxes / capsules); the slab
34
+ * intersection keeps that interval small even for kilometre-scale
35
+ * sweeps, so this isn't the limit it would otherwise be.
36
+ * @type {number}
37
+ */
38
+ const COARSE_STEPS = 32;
39
+
40
+ const swept_aabb = new Float64Array(6);
41
+ const local_aabb = new Float64Array(6);
42
+ const initial_aabb = new Float64Array(6);
43
+ const target_local_aabb = new Float64Array(6);
44
+ const target_world_aabb = new Float64Array(6);
45
+
46
+ // GJK simplex buffer (used to detect overlap during the bisection).
47
+ const simplex_buf = new Float64Array(12);
48
+
49
+ // Scratch for MPR's MTV output — same vec3 convention as EPA: direction
50
+ // is "A's interior into B" with the magnitude being the depth. We use
51
+ // MPR for the contact-normal recovery at the kiss point because it
52
+ // converges reliably on smooth-vs-smooth supports where EPA hits its
53
+ // iteration cap before tightening (sphere-vs-sphere shallow overlap).
54
+ const mpr_result = new Float64Array(3);
55
+
56
+ /**
57
+ * Long-lived PosedShape adapters for the swept shape and the
58
+ * candidate's collider. Both are re-bound per query — the swept one
59
+ * also gets its position field re-written every bisection step
60
+ * without re-running `setup()`, since shape and rotation are
61
+ * constant through the sweep.
62
+ */
63
+ const swept_posed = new PosedShape();
64
+ const candidate_posed = new PosedShape();
65
+
66
+ /**
67
+ * Buffer for the union of static-BVH and dynamic-BVH candidates that
68
+ * overlap the swept AABB. Grows by doubling on the rare overflow.
69
+ * @type {Uint32Array}
70
+ */
71
+ let scratch_candidates = new Uint32Array(64);
72
+
73
+ /**
74
+ * Sweep a convex shape along a ray and find the first body it would
75
+ * hit. Used by character controllers (sweep a capsule along intended
76
+ * movement to find blockers), high-speed projectile movement (avoid
77
+ * tunnelling without paying for full per-body CCD), and any kinematic
78
+ * "where would this body end up if I tried to move it?" query.
79
+ *
80
+ * Pipeline:
81
+ * 1. Build the swept AABB: the shape's local-frame AABB rotated
82
+ * into world by `rotation`, translated to `ray.origin`, then
83
+ * stretched along `ray.direction * ray.tMax`.
84
+ * 2. Query both broadphase BVHs (static + dynamic) for leaves whose
85
+ * AABB overlaps the swept volume.
86
+ * 3. For each candidate, bisect the [0, best_t] interval for the
87
+ * smallest `t` at which the swept shape overlaps the candidate's
88
+ * collider. GJK on each midpoint.
89
+ *
90
+ * Picking `best_t` (rather than always running the full [0, tMax]
91
+ * bisection) is the early-termination optimisation: once we have a
92
+ * hit at some t, no later candidate can produce a smaller TOI by
93
+ * being checked at its full [0, tMax] interval.
94
+ *
95
+ * Output on hit:
96
+ * - `result.t` — sweep distance to impact (in the same units as
97
+ * `ray.direction`'s magnitude; for a unit-direction ray, this is
98
+ * metres).
99
+ * - `result.position` — `ray.origin + t * ray.direction`, i.e. the
100
+ * centre of the swept shape at the moment of first contact. NOT
101
+ * a point on the target's surface; that would need narrowphase
102
+ * refinement which the broadphase-only architecture doesn't have
103
+ * wired up yet.
104
+ * - `result.normal` — target surface's outward normal at the kiss
105
+ * point (unit length), computed by running GJK + EPA against the
106
+ * just-overlapping configuration at the TOI. Falls back to
107
+ * `-ray.direction` on the rare case EPA degenerates (NaN / zero
108
+ * depth) — see comment near the EPA call.
109
+ * - `result.entity` / `result.body_id` — the hit body.
110
+ *
111
+ * Output on miss: untouched; same convention as `raycast`.
112
+ *
113
+ * @param {PhysicsSystem} system
114
+ * @param {Ray3} ray origin + unit direction + `tMax`
115
+ * @param {AbstractShape3D} shape shape being swept, in its local frame
116
+ * @param {{x:number,y:number,z:number,w:number}} rotation fixed orientation
117
+ * @param {PhysicsSurfacePoint} result populated on hit; untouched on miss
118
+ * @param {(entity:number, collider:Collider)=>boolean} [filter] mandatory in
119
+ * contract; defaults to {@link returnTrue}
120
+ * @returns {boolean}
121
+ */
122
+ export function shape_cast(system, ray, shape, rotation, result, filter = returnTrue) {
123
+ const ox = ray.origin_x;
124
+ const oy = ray.origin_y;
125
+ const oz = ray.origin_z;
126
+ const dx = ray.direction_x;
127
+ const dy = ray.direction_y;
128
+ const dz = ray.direction_z;
129
+ const tMax = ray.tMax;
130
+ if (!(tMax > 0) || !Number.isFinite(tMax)) return false;
131
+
132
+ const rx = rotation.x, ry = rotation.y, rz = rotation.z, rw = rotation.w;
133
+
134
+ // ── 1. Swept AABB ──────────────────────────────────────────────
135
+ shape.compute_bounding_box(local_aabb);
136
+ // Rotation around ray.origin gives the shape's initial AABB in
137
+ // world space.
138
+ aabb3_transform_oriented(
139
+ initial_aabb, 0,
140
+ local_aabb[0], local_aabb[1], local_aabb[2],
141
+ local_aabb[3], local_aabb[4], local_aabb[5],
142
+ ox, oy, oz,
143
+ rx, ry, rz, rw
144
+ );
145
+ // Stretch by the sweep displacement. Positive direction components
146
+ // push the +face out; negative components push the −face out.
147
+ swept_aabb[0] = initial_aabb[0];
148
+ swept_aabb[1] = initial_aabb[1];
149
+ swept_aabb[2] = initial_aabb[2];
150
+ swept_aabb[3] = initial_aabb[3];
151
+ swept_aabb[4] = initial_aabb[4];
152
+ swept_aabb[5] = initial_aabb[5];
153
+ if (dx > 0) swept_aabb[3] += dx * tMax; else swept_aabb[0] += dx * tMax;
154
+ if (dy > 0) swept_aabb[4] += dy * tMax; else swept_aabb[1] += dy * tMax;
155
+ if (dz > 0) swept_aabb[5] += dz * tMax; else swept_aabb[2] += dz * tMax;
156
+
157
+ // ── 2. Broadphase: gather candidates from both trees ───────────
158
+ const n_static = bvh_query_user_data_overlaps_aabb(
159
+ scratch_candidates, 0, system.staticBvh, swept_aabb
160
+ );
161
+ const n_dynamic = bvh_query_user_data_overlaps_aabb(
162
+ scratch_candidates, n_static, system.dynamicBvh, swept_aabb
163
+ );
164
+ const n_total = n_static + n_dynamic;
165
+ if (n_total === 0) return false;
166
+
167
+ // ── 3. Narrowphase: per-candidate bisection ────────────────────
168
+ //
169
+ // Re-bind the swept shape's PosedShape once for shape + rotation;
170
+ // we update `px/py/pz` per bisection midpoint without rerunning
171
+ // setup (px/py/pz are public fields by contract).
172
+ swept_posed.shape = shape;
173
+ swept_posed.qx = rx; swept_posed.qy = ry; swept_posed.qz = rz; swept_posed.qw = rw;
174
+
175
+ let best_t = tMax;
176
+ let best_body_id = -1;
177
+ let best_entity = -1;
178
+
179
+ for (let i = 0; i < n_total; i++) {
180
+ const body_id = scratch_candidates[i];
181
+ const body_idx = body_id_index(body_id);
182
+ const entity = system.entityOf(body_id);
183
+ if (entity < 0) continue;
184
+
185
+ const collider = system.__primary_collider(body_idx);
186
+ if (collider === null) continue;
187
+ if (!filter(entity, collider)) continue;
188
+
189
+ const target_tr = system.__transforms[body_idx];
190
+ candidate_posed.setup(collider.shape, target_tr.position, target_tr.rotation);
191
+
192
+ // Already overlapping at t = 0? Return immediately — can't beat
193
+ // this, and the bisection's "no overlap at lo, overlap at hi"
194
+ // invariant doesn't hold.
195
+ swept_posed.px = ox; swept_posed.py = oy; swept_posed.pz = oz;
196
+ if (gjk(simplex_buf, swept_posed, candidate_posed)) {
197
+ best_t = 0;
198
+ best_body_id = body_id;
199
+ best_entity = entity;
200
+ break;
201
+ }
202
+
203
+ // ── Slab intersection: narrow the search interval analytically ─
204
+ //
205
+ // Compute the body's tight world AABB and intersect it with the
206
+ // swept shape's AABB sliding along the sweep direction. The
207
+ // result is the t-interval `[t_aabb_enter, t_aabb_exit]` during
208
+ // which the two AABBs overlap. Outside this interval the actual
209
+ // shapes can't overlap either (AABB is a superset of the
210
+ // shape), so we get to skip all of [0, t_aabb_enter) and
211
+ // (t_aabb_exit, best_t] without testing.
212
+ //
213
+ // This is the key to making `shapeCast` scale to long sweeps:
214
+ // a 1 km cast of a 1 m sphere against another 1 m sphere has an
215
+ // AABB-overlap window of ≈ 2 m (the sum of diameters along the
216
+ // sweep axis), reducing the coarse-step grid from a 30 m
217
+ // resolution (1000/32) to 6 cm (2/32).
218
+ collider.shape.compute_bounding_box(target_local_aabb);
219
+ aabb3_transform_oriented(
220
+ target_world_aabb, 0,
221
+ target_local_aabb[0], target_local_aabb[1], target_local_aabb[2],
222
+ target_local_aabb[3], target_local_aabb[4], target_local_aabb[5],
223
+ target_tr.position.x, target_tr.position.y, target_tr.position.z,
224
+ target_tr.rotation.x, target_tr.rotation.y, target_tr.rotation.z, target_tr.rotation.w
225
+ );
226
+
227
+ let t_aabb_enter = -Infinity;
228
+ let t_aabb_exit = Infinity;
229
+ let skip_candidate = false;
230
+ for (let ax = 0; ax < 3; ax++) {
231
+ const init_min = initial_aabb[ax];
232
+ const init_max = initial_aabb[ax + 3];
233
+ const body_min = target_world_aabb[ax];
234
+ const body_max = target_world_aabb[ax + 3];
235
+ const d_ax = ax === 0 ? dx : (ax === 1 ? dy : dz);
236
+ if (d_ax === 0) {
237
+ // Static along this axis: AABBs must overlap in their
238
+ // initial position or this candidate is unreachable.
239
+ if (init_max < body_min || init_min > body_max) {
240
+ skip_candidate = true;
241
+ break;
242
+ }
243
+ continue;
244
+ }
245
+ // swept_min(t) = init_min + d·t, swept_max(t) = init_max + d·t.
246
+ // Slab-enter: swept_max(t) = body_min → t = (body_min − init_max) / d
247
+ // Slab-exit: swept_min(t) = body_max → t = (body_max − init_min) / d
248
+ const t1 = (body_min - init_max) / d_ax;
249
+ const t2 = (body_max - init_min) / d_ax;
250
+ const lo = t1 < t2 ? t1 : t2;
251
+ const hi = t1 < t2 ? t2 : t1;
252
+ if (lo > t_aabb_enter) t_aabb_enter = lo;
253
+ if (hi < t_aabb_exit) t_aabb_exit = hi;
254
+ }
255
+ if (skip_candidate) continue;
256
+ if (t_aabb_enter > t_aabb_exit) continue;
257
+
258
+ // Clamp to the relevant t-window: [0, best_t]. Anything beyond
259
+ // best_t can't tighten the answer.
260
+ const t_start = t_aabb_enter > 0 ? t_aabb_enter : 0;
261
+ const t_end = t_aabb_exit < best_t ? t_aabb_exit : best_t;
262
+ if (t_start >= t_end) continue;
263
+
264
+ // Coarse-search within the narrowed interval to find any t for
265
+ // which the shapes overlap (their AABBs do by construction; the
266
+ // shapes themselves may not, e.g. two spheres passing alongside).
267
+ const step_dt = (t_end - t_start) / COARSE_STEPS;
268
+ let t_prev = t_start;
269
+ let t_hi = -1;
270
+ for (let s = 1; s <= COARSE_STEPS; s++) {
271
+ const t = t_start + s * step_dt;
272
+ swept_posed.px = ox + dx * t;
273
+ swept_posed.py = oy + dy * t;
274
+ swept_posed.pz = oz + dz * t;
275
+ if (gjk(simplex_buf, swept_posed, candidate_posed)) {
276
+ t_hi = t;
277
+ break;
278
+ }
279
+ t_prev = t;
280
+ }
281
+ if (t_hi < 0) continue;
282
+
283
+ // Bisect [t_prev, t_hi]: invariant is `gjk(t_lo) === false` and
284
+ // `gjk(t_hi) === true`.
285
+ let t_lo = t_prev;
286
+ const stop_width = TOI_TOLERANCE * tMax;
287
+ for (let bi = 0; bi < MAX_BISECTION && (t_hi - t_lo) > stop_width; bi++) {
288
+ const t_mid = (t_lo + t_hi) * 0.5;
289
+ swept_posed.px = ox + dx * t_mid;
290
+ swept_posed.py = oy + dy * t_mid;
291
+ swept_posed.pz = oz + dz * t_mid;
292
+ if (gjk(simplex_buf, swept_posed, candidate_posed)) {
293
+ t_hi = t_mid;
294
+ } else {
295
+ t_lo = t_mid;
296
+ }
297
+ }
298
+
299
+ // Use t_lo (the just-SEPARATING side of the bisection) rather
300
+ // than t_hi (just-overlapping). With the fixed GJK that no
301
+ // longer flags 5 mm-clear gaps as overlap, the bisection is
302
+ // tight; reporting t_lo gives kinematic callers a conservative
303
+ // TOI — the swept shape at the reported moment is provably NOT
304
+ // overlapping the target. The normal-probe below steps a few
305
+ // mm past best_t to get a valid simplex for MPR.
306
+ if (t_lo < best_t) {
307
+ best_t = t_lo;
308
+ best_body_id = body_id;
309
+ best_entity = entity;
310
+ }
311
+ }
312
+
313
+ if (best_body_id === -1) return false;
314
+
315
+ const rp = result.position;
316
+ const rn = result.normal;
317
+
318
+ const swept_x = ox + dx * best_t;
319
+ const swept_y = oy + dy * best_t;
320
+ const swept_z = oz + dz * best_t;
321
+
322
+ rp[0] = swept_x;
323
+ rp[1] = swept_y;
324
+ rp[2] = swept_z;
325
+
326
+ // ── Contact-normal recovery via MPR ────────────────────────────
327
+ //
328
+ // Bisection converges with `swept_posed` somewhere within
329
+ // `stop_width = TOI_TOLERANCE * tMax` of the true contact. At
330
+ // exactly `best_t` the overlap depth is therefore sub-millimetre
331
+ // on practical sweeps. EPA on smooth-vs-smooth supports at this
332
+ // overlap can't tighten before its iteration cap and returns a
333
+ // direction off by ~30° from truth. MPR is the better tool here:
334
+ // it converges in 5-15 iterations on smooth surfaces, same MTV
335
+ // output convention as EPA (direction = A's interior into B,
336
+ // magnitude = depth).
337
+ //
338
+ // We still advance the swept centre by NORMAL_PROBE_OFFSET past
339
+ // best_t (a few mm into the target) before sampling — the extra
340
+ // overlap gives both MPR and the body-centre sign check a more
341
+ // robust geometric basis. The reported `result.t` stays at
342
+ // `best_t`; only the normal probe moves.
343
+ //
344
+ // Sign-validation: MPR's polytope can also settle on either side
345
+ // of the origin for highly symmetric configurations, so we dot
346
+ // against the body-centre axis and flip if needed (same pattern
347
+ // as `narrowphase_step`'s EPA path). The stored normal is the
348
+ // target's outward surface normal — `B → A` direction — i.e. the
349
+ // negated (validated) MTV.
350
+ const NORMAL_PROBE_OFFSET = 0.01;
351
+ const probe_t = best_t + NORMAL_PROBE_OFFSET;
352
+ const probe_x = ox + dx * probe_t;
353
+ const probe_y = oy + dy * probe_t;
354
+ const probe_z = oz + dz * probe_t;
355
+ const best_body_idx = body_id_index(best_body_id);
356
+ const best_collider = system.__primary_collider(best_body_idx);
357
+ let normal_x = -dx, normal_y = -dy, normal_z = -dz;
358
+ if (best_collider !== null) {
359
+ const best_tr = system.__transforms[best_body_idx];
360
+ candidate_posed.setup(best_collider.shape, best_tr.position, best_tr.rotation);
361
+ swept_posed.px = probe_x;
362
+ swept_posed.py = probe_y;
363
+ swept_posed.pz = probe_z;
364
+ if (mpr(mpr_result, 0, swept_posed, candidate_posed)) {
365
+ let ex = mpr_result[0], ey = mpr_result[1], ez = mpr_result[2];
366
+ const depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
367
+ if (depth > 0 && Number.isFinite(depth)) {
368
+ const ab_x = best_tr.position.x - probe_x;
369
+ const ab_y = best_tr.position.y - probe_y;
370
+ const ab_z = best_tr.position.z - probe_z;
371
+ if (ex * ab_x + ey * ab_y + ez * ab_z < 0) {
372
+ ex = -ex; ey = -ey; ez = -ez;
373
+ }
374
+ const inv = 1 / depth;
375
+ normal_x = -ex * inv;
376
+ normal_y = -ey * inv;
377
+ normal_z = -ez * inv;
378
+ }
379
+ }
380
+ }
381
+
382
+ rn[0] = normal_x; rn[1] = normal_y; rn[2] = normal_z;
383
+ result.t = best_t;
384
+ result.entity = best_entity;
385
+ result.body_id = best_body_id;
386
+ return true;
387
+ }
@@ -1,36 +1,122 @@
1
1
  /**
2
- * Sequential-impulse contact solver. Partitions the touched contact set into
3
- * islands (via {@link IslandBuilder}, owned by the PhysicsSystem and rebuilt
4
- * upstream in the pipeline), then iterates each island independently.
5
- *
6
- * Per-island iteration matters for two reasons:
7
- * 1. Impulse propagation converges inside an island without waiting for
8
- * unrelated bodies' Gauss-Seidel updates from previous outer loops.
9
- * 2. Disconnected awake bodies don't pay each other's solver cost — adding
10
- * an unrelated active body to the scene scales O(island_size) rather than
11
- * O(global_active).
12
- *
13
- * Coulomb friction is applied as a disk clamp in the contact tangent plane.
14
- * Position correction is folded into the velocity solve via Baumgarte bias.
15
- *
16
- * The `apply_warm_start` flag gates the warm-start impulse application.
17
- * In a classic PGS call (one solve per tick), pass `true` (default) the
18
- * cached impulses from the previous tick get replayed onto the velocity,
19
- * seeding the iterations near the converged answer. In TGS substepping,
20
- * the PhysicsSystem calls this once per substep; only substep 0 should
21
- * apply warm-start, because subsequent substeps continue from the solver
22
- * state left by the previous substep (the impulses in `data` already
23
- * reflect the current velocity, so re-applying them would double-count).
24
- *
25
- * Inertial: angular response uses the full world-frame inverse inertia
26
- * `R · diag(I⁻¹_local) · R^T` via {@link world_inverse_inertia_apply},
27
- * correct for arbitrary rotations + diagonal local inertia.
2
+ * Stage 1 prepare the contact constraints for an outer step.
3
+ *
4
+ * Packs every touched, non-sensor contact into the flat scratch arrays:
5
+ * local-frame witness anchors, tangent basis, effective masses, friction,
6
+ * the restitution approach velocity, and the warm-start replay (applied
7
+ * once here, not per substep). Computes the SPOOK gains from the SUBSTEP
8
+ * size `dt_sub` because the position correction runs once per substep.
9
+ *
10
+ * @param {ManifoldStore} manifolds
11
+ * @param {PhysicsSystem} system
12
+ * @param {number} dt_sub substep size `dt / substeps`
13
+ * @returns {number} number of contacts prepared (also stored module-side)
14
+ */
15
+ export function prepare_contacts(manifolds: ManifoldStore, system: PhysicsSystem, dt_sub: number): number;
16
+ /**
17
+ * Stage 1b (per substep) warm-start: replay the cached accumulated
18
+ * impulses `(j_n, j_t1, j_t2)` onto persistent velocity using the current
19
+ * (refreshed) lever arms and tangents. Run once per substep, after
20
+ * {@link refresh_contacts} and before {@link solve_velocity}.
21
+ *
22
+ * Per-substep warm-start is the crux of stable TGS: the stored impulse is a
23
+ * per-substep quantity (≈ the impulse to counter one substep of gravity), so
24
+ * replaying it each substep balances that substep's `integrate_velocity_gravity`
25
+ * and a resting contact holds at zero velocity. `solve_velocity` then only
26
+ * has to correct the residual, which converges in a few iterations even for
27
+ * deep chains because each substep carries just `h` of gravity.
28
+ *
29
+ * @param {ManifoldStore} manifolds
30
+ * @param {PhysicsSystem} system
31
+ */
32
+ export function warm_start_contacts(manifolds: ManifoldStore, system: PhysicsSystem): void;
33
+ /**
34
+ * Stage 2 (per substep) — re-derive each contact's geometry from the
35
+ * bodies' current poses and the local witness anchors captured at prepare.
36
+ *
37
+ * For each contact:
38
+ * - rotate the stored local witnesses back to world by the current pose to
39
+ * get the moved contact points `cpA`, `cpB`;
40
+ * - current penetration `depth_now = depth0 − Δseparation`, where
41
+ * `Δseparation = (cpA − wA − (cpB − wB)) · n` is the change since prepare.
42
+ * Anchoring on the trusted prepare-time depth makes the sign convention
43
+ * irrelevant — only the analytic delta uses the anchors;
44
+ * - rebuild the impulse levers `rA`/`rB` from the moved midpoint;
45
+ * - recompute the position-correction bias from `depth_now`.
46
+ *
47
+ * The contact normal and tangents are held fixed for the outer step (valid
48
+ * for the small per-step rotation), so they are not recomputed here.
49
+ *
50
+ * @param {ManifoldStore} manifolds
51
+ * @param {PhysicsSystem} system
52
+ */
53
+ export function refresh_contacts(manifolds: ManifoldStore, system: PhysicsSystem): void;
54
+ /**
55
+ * Stage 3 (per substep) — velocity iterations enforcing pure
56
+ * non-penetration (`vn → 0`) plus Coulomb-disk friction. No bias: depth
57
+ * correction is the position pass, restitution is the one-shot pass.
58
+ *
59
+ * @param {ManifoldStore} manifolds
60
+ * @param {PhysicsSystem} system
61
+ * @param {number} iters
62
+ */
63
+ export function solve_velocity(manifolds: ManifoldStore, system: PhysicsSystem, iters: number): void;
64
+ /**
65
+ * Stage 4 (once, after the substep loop) — one-shot restitution
66
+ * (Box2D-v3 `b2ApplyRestitution`, Catto 2018). Drives `vn → -e · vn_approach`
67
+ * exactly once per closing contact, gated on (a) the contact having been
68
+ * closing faster than the threshold at prepare, and (b) a compressive
69
+ * normal impulse having formed during the velocity solve. The added impulse
70
+ * accumulates into the same normal-impulse slot so it composes with
71
+ * warm-start next frame.
28
72
  *
29
73
  * @param {ManifoldStore} manifolds
30
- * @param {PhysicsSystem} system PhysicsSystem; reads `system.islands`.
31
- * @param {number} dt step duration (full tick `dt` for PGS, sub-tick for TGS)
74
+ * @param {PhysicsSystem} system
75
+ */
76
+ export function apply_restitution(manifolds: ManifoldStore, system: PhysicsSystem): void;
77
+ /**
78
+ * Stage 5 (per substep) — split-impulse position correction. Normal-only
79
+ * (friction in the pseudo-velocity pass is ill-defined). Reads the per-body
80
+ * pseudo-velocity, applies a clamped normal impulse driven by the refreshed
81
+ * `bias_position`, and writes the increment back into the pseudo buffer. The
82
+ * pose integrator folds pseudo-velocity into `pos += v · dt` and discards it.
83
+ *
84
+ * The pseudo buffer must be zeroed by the caller before this stage each
85
+ * substep (it's a per-substep correction). The position accumulator
86
+ * `scratch_pos_jn` is likewise reset here per substep.
87
+ *
88
+ * @param {ManifoldStore} manifolds
89
+ * @param {PhysicsSystem} system
90
+ * @param {number} pos_iters
91
+ */
92
+ export function solve_position(manifolds: ManifoldStore, system: PhysicsSystem, pos_iters: number): void;
93
+ /**
94
+ * Convenience single-step driver: prepare → refresh → velocity →
95
+ * restitution → position, all at the full `dt` (one substep). Equivalent to
96
+ * the Phase-2 solver. The substepped path in `PhysicsSystem.fixedUpdate`
97
+ * calls the stages directly; this entry point exists for callers/tests that
98
+ * want a one-shot solve.
99
+ *
100
+ * The position pass writes `system.__pseudo_velocity`; the caller must zero
101
+ * that buffer before this call and fold it into the pose afterwards.
102
+ *
103
+ * @param {ManifoldStore} manifolds
104
+ * @param {PhysicsSystem} system
105
+ * @param {number} dt
32
106
  * @param {number} [iters]
33
- * @param {boolean} [apply_warm_start]
107
+ * @param {number} [pos_iters]
108
+ */
109
+ export function solve_contacts(manifolds: ManifoldStore, system: PhysicsSystem, dt: number, iters?: number, pos_iters?: number): void;
110
+ /**
111
+ * Velocity-iteration count per substep. With substepping the per-substep
112
+ * count can be lower than a single-step PGS solver would need, because the
113
+ * outer loop revisits the contact set `substeps` times.
114
+ * @type {number}
115
+ */
116
+ export const DEFAULT_VELOCITY_ITERATIONS: number;
117
+ /**
118
+ * Position-iteration count per substep (split-impulse pseudo-velocity pass).
119
+ * @type {number}
34
120
  */
35
- export function solve_contacts(manifolds: ManifoldStore, system: PhysicsSystem, dt: number, iters?: number, apply_warm_start?: boolean): void;
121
+ export const DEFAULT_POSITION_ITERATIONS: number;
36
122
  //# sourceMappingURL=solve_contacts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"solve_contacts.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/solver/solve_contacts.js"],"names":[],"mappings":"AAyQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,oFAJW,MAAM,UACN,MAAM,qBACN,OAAO,QAoBjB"}
1
+ {"version":3,"file":"solve_contacts.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/solver/solve_contacts.js"],"names":[],"mappings":"AA8ZA;;;;;;;;;;;;;GAaG;AACH,0FAHW,MAAM,GACJ,MAAM,CA4IlB;AAED;;;;;;;;;;;;;;;GAeG;AACH,2FAuCC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wFA2DC;AAED;;;;;;;;GAQG;AACH,uFAFW,MAAM,QA2GhB;AAED;;;;;;;;;;;GAWG;AACH,yFA4DC;AAED;;;;;;;;;;;;;;GAcG;AACH,2FAFW,MAAM,QA4EhB;AAED;;;;;;;;;;;;;;;GAeG;AACH,oFAJW,MAAM,UACN,MAAM,cACN,MAAM,QAYhB;AAl7BD;;;;;GAKG;AACH,0CAFU,MAAM,CAEuB;AAEvC;;;GAGG;AACH,0CAFU,MAAM,CAEsB"}