@woosh/meep-engine 2.147.0 → 2.149.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 (61) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.d.ts +23 -0
  3. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.d.ts.map +1 -0
  4. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.js +295 -0
  5. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts +4 -4
  6. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts.map +1 -1
  7. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.js +48 -52
  8. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts +23 -21
  9. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts.map +1 -1
  10. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.js +41 -406
  11. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts +5 -4
  12. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts.map +1 -1
  13. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.js +400 -395
  14. package/src/engine/navigation/mesh/NavigationMesh.d.ts +6 -2
  15. package/src/engine/navigation/mesh/NavigationMesh.d.ts.map +1 -1
  16. package/src/engine/navigation/mesh/NavigationMesh.js +234 -212
  17. package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts +7 -3
  18. package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts.map +1 -1
  19. package/src/engine/navigation/mesh/bt_mesh_face_find_path.js +67 -73
  20. package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts +16 -5
  21. package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts.map +1 -1
  22. package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.js +262 -147
  23. package/src/engine/navigation/mesh/build/navmesh_build_topology.d.ts.map +1 -1
  24. package/src/engine/navigation/mesh/build/navmesh_build_topology.js +33 -3
  25. package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts +4 -1
  26. package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts.map +1 -1
  27. package/src/engine/navigation/mesh/bvh_query_nearest_face.js +164 -131
  28. package/src/engine/physics/body/SolverBodyState.d.ts +142 -0
  29. package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -0
  30. package/src/engine/physics/body/SolverBodyState.js +251 -0
  31. package/src/engine/physics/broadphase/generate_pairs.d.ts +2 -1
  32. package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
  33. package/src/engine/physics/broadphase/generate_pairs.js +110 -108
  34. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  35. package/src/engine/physics/constraint/solve_constraints.js +691 -673
  36. package/src/engine/physics/ecs/PhysicsSystem.d.ts +21 -18
  37. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  38. package/src/engine/physics/ecs/PhysicsSystem.js +223 -91
  39. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +23 -0
  40. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  41. package/src/engine/physics/inertia/world_inverse_inertia.js +116 -77
  42. package/src/engine/physics/integration/integrate_position.d.ts +11 -1
  43. package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
  44. package/src/engine/physics/integration/integrate_position.js +97 -79
  45. package/src/engine/physics/integration/integrate_velocity.d.ts +12 -3
  46. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
  47. package/src/engine/physics/integration/integrate_velocity.js +201 -160
  48. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  49. package/src/engine/physics/narrowphase/box_box_manifold.js +750 -665
  50. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
  51. package/src/engine/physics/narrowphase/box_triangle_contact.js +4 -34
  52. package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts +16 -0
  53. package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts.map +1 -0
  54. package/src/engine/physics/narrowphase/clip_against_axis_uv.js +49 -0
  55. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  56. package/src/engine/physics/narrowphase/narrowphase_step.js +24 -3
  57. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  58. package/src/engine/physics/queries/raycast.js +201 -198
  59. package/src/engine/physics/solver/solve_contacts.d.ts +2 -2
  60. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  61. package/src/engine/physics/solver/solve_contacts.js +1341 -1173
@@ -0,0 +1,251 @@
1
+ import { ceilPowerOfTwo } from "../../../core/binary/operations/ceilPowerOfTwo.js";
2
+ import { BodyKind } from "../ecs/BodyKind.js";
3
+
4
+ /**
5
+ * # SolverBodyState — data-oriented mirror of the per-body solver hot state
6
+ *
7
+ * The TGS substep window (contact solver, joint solver, gravity / position
8
+ * integration) reads and writes a small, fixed set of per-body quantities
9
+ * thousands of times per step: linear / angular velocity, inverse mass,
10
+ * inverse-inertia diagonal, and orientation. In the object world those live on
11
+ * the {@link RigidBody} and {@link Transform} *components*, reached via
12
+ * `system.__bodies[idx]` / `system.__transforms[idx]` — a chain of pointer
13
+ * dereferences (array → component object → `Vector3` field) on the hottest
14
+ * inner loop in the engine.
15
+ *
16
+ * This class packs exactly those quantities into one ArrayBuffer-backed,
17
+ * stride-{@link SBS_STRIDE} `Float64Array` indexed by **body slot index** (the
18
+ * same index the rest of the physics system already uses). A body's whole
19
+ * solver state sits in one contiguous span, so the solver reads it with flat
20
+ * typed-array indexing and no object dereference.
21
+ *
22
+ * ## Lifecycle within one `fixedUpdate`
23
+ *
24
+ * - {@link begin} once, after islands are built: ensures capacity and rolls
25
+ * the per-step gather stamp.
26
+ * - {@link gather} every body referenced this step (the awake set + the
27
+ * static / kinematic anchors referenced by contacts and joints). Idempotent
28
+ * per step via the stamp, so callers may gather the same body redundantly.
29
+ * MUST run after the once-per-step force integration (so the gathered
30
+ * velocity is post-force) and before any substep stage reads it.
31
+ * - the substep loop mutates `data` in place.
32
+ * - {@link scatter} once at the end: write the persistent linear / angular
33
+ * velocity back to each gathered body's `RigidBody`. Pose is NOT scattered
34
+ * here — `integrate_position` keeps the authoritative pose on the
35
+ * `Transform` (write-through, so its `onChanged` subscribers and the
36
+ * per-substep concave re-detection still see the moved pose); the
37
+ * orientation quaternion is mirrored into `data` purely for the impulse
38
+ * loop's world-inverse-inertia evaluation.
39
+ *
40
+ * ## Material content
41
+ *
42
+ * For Static / Kinematic bodies `invMass` and the inverse-inertia diagonal are
43
+ * stored as **zero** regardless of the component values — the solver treats
44
+ * "no inverse mass / no inverse inertia" as immovable, which is exactly how the
45
+ * object path's `kind !== Dynamic` guards behave. Velocity is mirrored as-is
46
+ * (a kinematic mover carries a real velocity the contact rows must see; a
47
+ * static reads zero). Scattering velocity back to a non-dynamic body is a
48
+ * no-op in value (the solver never moves it), so scatter need not special-case.
49
+ *
50
+ * The arithmetic the solver performs on this data is identical to the object
51
+ * path — only the *source of the operands* changes — so the engine's
52
+ * same-runtime bit-identical determinism contract is preserved.
53
+ *
54
+ * @author Alex Goldring
55
+ * @copyright Company Named Limited (c) 2026
56
+ */
57
+
58
+ /** Inverse mass (0 for Static / Kinematic). */
59
+ export const SBS_INV_MASS = 0;
60
+ /** Inverse-inertia diagonal, body-local principal frame (0,0,0 for non-dynamic). */
61
+ export const SBS_INV_I_X = 1;
62
+ export const SBS_INV_I_Y = 2;
63
+ export const SBS_INV_I_Z = 3;
64
+ /** Orientation quaternion (mirrors `transform.rotation`, refreshed per substep). */
65
+ export const SBS_QX = 4;
66
+ export const SBS_QY = 5;
67
+ export const SBS_QZ = 6;
68
+ export const SBS_QW = 7;
69
+ /** Persistent linear velocity. */
70
+ export const SBS_LV_X = 8;
71
+ export const SBS_LV_Y = 9;
72
+ export const SBS_LV_Z = 10;
73
+ /** Persistent angular velocity. */
74
+ export const SBS_AV_X = 11;
75
+ export const SBS_AV_Y = 12;
76
+ export const SBS_AV_Z = 13;
77
+
78
+ /** Doubles per body. */
79
+ export const SBS_STRIDE = 14;
80
+
81
+ export class SolverBodyState {
82
+ constructor(initial_capacity = 16) {
83
+ const cap = ceilPowerOfTwo(Math.max(4, initial_capacity));
84
+
85
+ /**
86
+ * Interleaved per-body state, `slot_index * SBS_STRIDE + field`.
87
+ * @type {Float64Array}
88
+ */
89
+ this.data = new Float64Array(cap * SBS_STRIDE);
90
+
91
+ /**
92
+ * Per-body "gathered this step" stamp; equals {@link __gen} when the
93
+ * body's slot has been gathered in the current step.
94
+ * @private
95
+ * @type {Int32Array}
96
+ */
97
+ this.__stamp = new Int32Array(cap);
98
+
99
+ /**
100
+ * Dense list of slot indices gathered this step, for {@link scatter}.
101
+ * @private
102
+ * @type {Uint32Array}
103
+ */
104
+ this.__gathered = new Uint32Array(cap);
105
+
106
+ /** @private */
107
+ this.__gathered_count = 0;
108
+
109
+ /**
110
+ * Monotonic per-step generation. {@link begin} increments it so the
111
+ * previous step's stamps are stale without an O(n) clear.
112
+ * @private
113
+ */
114
+ this.__gen = 0;
115
+
116
+ /** @private */
117
+ this.__capacity = cap;
118
+ }
119
+
120
+ /**
121
+ * Number of bodies gathered in the current step.
122
+ * @returns {number}
123
+ */
124
+ get gathered_count() {
125
+ return this.__gathered_count;
126
+ }
127
+
128
+ /**
129
+ * Ensure the typed arrays can index slot `[0, hwm)` and roll the per-step
130
+ * gather state. Call once per `fixedUpdate`, after islands are built.
131
+ *
132
+ * @param {number} hwm body-storage high-water mark
133
+ */
134
+ begin(hwm) {
135
+ if (hwm > this.__capacity) {
136
+ this.__grow(hwm);
137
+ }
138
+
139
+ // Roll the generation; on (astronomically unlikely) Int32 wrap, clear
140
+ // stamps so a recycled generation value can't read as "gathered".
141
+ const next = (this.__gen + 1) | 0;
142
+ if (next <= 0) {
143
+ this.__stamp.fill(0);
144
+ this.__gen = 1;
145
+ } else {
146
+ this.__gen = next;
147
+ }
148
+ this.__gathered_count = 0;
149
+ }
150
+
151
+ /**
152
+ * Mirror one body's solver state into `data` if not already gathered this
153
+ * step. Idempotent per step. Reads the body's current (post-force)
154
+ * velocity and current pose.
155
+ *
156
+ * @param {number} idx body slot index
157
+ * @param {RigidBody} rb
158
+ * @param {Transform} transform
159
+ */
160
+ gather(idx, rb, transform) {
161
+ const stamp = this.__stamp;
162
+ if (stamp[idx] === this.__gen) {
163
+ return;
164
+ }
165
+ stamp[idx] = this.__gen;
166
+ this.__gathered[this.__gathered_count++] = idx;
167
+
168
+ const d = this.data;
169
+ const base = idx * SBS_STRIDE;
170
+
171
+ const dynamic = rb.kind === BodyKind.Dynamic;
172
+ if (dynamic) {
173
+ d[base + SBS_INV_MASS] = rb.mass > 0 ? 1 / rb.mass : 0;
174
+ const ii = rb.inverseInertiaLocal;
175
+ d[base + SBS_INV_I_X] = ii.x;
176
+ d[base + SBS_INV_I_Y] = ii.y;
177
+ d[base + SBS_INV_I_Z] = ii.z;
178
+ } else {
179
+ // Static / Kinematic: immovable to the solver — no inverse mass,
180
+ // no inverse inertia (mirrors the object path's `kind` guards).
181
+ d[base + SBS_INV_MASS] = 0;
182
+ d[base + SBS_INV_I_X] = 0;
183
+ d[base + SBS_INV_I_Y] = 0;
184
+ d[base + SBS_INV_I_Z] = 0;
185
+ }
186
+
187
+ const q = transform.rotation;
188
+ d[base + SBS_QX] = q[0];
189
+ d[base + SBS_QY] = q[1];
190
+ d[base + SBS_QZ] = q[2];
191
+ d[base + SBS_QW] = q[3];
192
+
193
+ const lv = rb.linearVelocity;
194
+ d[base + SBS_LV_X] = lv[0];
195
+ d[base + SBS_LV_Y] = lv[1];
196
+ d[base + SBS_LV_Z] = lv[2];
197
+
198
+ const av = rb.angularVelocity;
199
+ d[base + SBS_AV_X] = av[0];
200
+ d[base + SBS_AV_Y] = av[1];
201
+ d[base + SBS_AV_Z] = av[2];
202
+ }
203
+
204
+ /**
205
+ * Write the solved persistent linear / angular velocity back onto every
206
+ * gathered body's `RigidBody`. Direct typed-array writes into the
207
+ * `Vector3` backing — the same observer-bypassing path the solver already
208
+ * uses for velocity (`lv[0] += …`), so no `onChanged` semantics change.
209
+ *
210
+ * @param {RigidBody[]} bodies sparse, indexed by slot
211
+ */
212
+ scatter(bodies) {
213
+ const d = this.data;
214
+ const list = this.__gathered;
215
+ const n = this.__gathered_count;
216
+ for (let i = 0; i < n; i++) {
217
+ const idx = list[i];
218
+ const rb = bodies[idx];
219
+ if (rb === undefined) continue;
220
+ const base = idx * SBS_STRIDE;
221
+ const lv = rb.linearVelocity;
222
+ lv[0] = d[base + SBS_LV_X];
223
+ lv[1] = d[base + SBS_LV_Y];
224
+ lv[2] = d[base + SBS_LV_Z];
225
+ const av = rb.angularVelocity;
226
+ av[0] = d[base + SBS_AV_X];
227
+ av[1] = d[base + SBS_AV_Y];
228
+ av[2] = d[base + SBS_AV_Z];
229
+ }
230
+ }
231
+
232
+ /**
233
+ * @private
234
+ * @param {number} hwm
235
+ */
236
+ __grow(hwm) {
237
+ const cap = ceilPowerOfTwo(hwm);
238
+ const next_data = new Float64Array(cap * SBS_STRIDE);
239
+ const next_stamp = new Int32Array(cap);
240
+ const next_gathered = new Uint32Array(cap);
241
+ // No need to copy `data` — it is fully repopulated by `gather` each
242
+ // step before any read. `__stamp` must NOT carry stale gen values into
243
+ // the grown region; a fresh zero-filled array plus the live `__gen`
244
+ // (always >= 1 after begin) guarantees grown slots read as un-gathered.
245
+ next_stamp.set(this.__stamp);
246
+ this.data = next_data;
247
+ this.__stamp = next_stamp;
248
+ this.__gathered = next_gathered;
249
+ this.__capacity = cap;
250
+ }
251
+ }
@@ -28,11 +28,12 @@
28
28
  * slot, no narrowphase, no contact events. Existing cached
29
29
  * manifolds for a rejected pair time out naturally via the
30
30
  * manifold-store grace counter.
31
+ * @param {*} [pair_filter_this_arg]
31
32
  */
32
33
  export function generate_pairs(storage: BodyStorage, dynamic_bvh: BVH, static_bvh: BVH, manifolds: ManifoldStore, body_collider_lists: Array<Array<{
33
34
  collider: Collider;
34
35
  transform: Transform;
35
36
  entity: number;
36
37
  bvhNode: number;
37
- }>>, pair_list_out: PairList, pair_filter?: (idA: number, idB: number) => boolean): void;
38
+ }>>, pair_list_out: PairList, pair_filter?: (idA: number, idB: number) => boolean, pair_filter_this_arg?: any): void;
38
39
  //# sourceMappingURL=generate_pairs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate_pairs.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/broadphase/generate_pairs.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,uIARW,MAAM,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAY;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAC,CAAC,+CAElF,MAAM,OAAO,MAAM,KAAK,OAAO,QA4EhD"}
1
+ {"version":3,"file":"generate_pairs.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/broadphase/generate_pairs.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,uIATW,MAAM,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAY;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAC,CAAC,+CAElF,MAAM,OAAO,MAAM,KAAK,OAAO,oCA8EhD"}
@@ -1,108 +1,110 @@
1
- import { assert } from "../../../core/assert.js";
2
- import { bvh_query_user_data_overlaps_aabb } from "../../../core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js";
3
-
4
- const scratch_aabb = new Float64Array(6);
5
- const candidates = new Uint32Array(1024);
6
-
7
- /**
8
- * Iterate every awake body, query both BVHs from each of its attached
9
- * collider leaves, dedupe via the {@link ManifoldStore}, optionally gate
10
- * each candidate through a user-supplied filter, and append each surviving
11
- * body-pair to `pair_list_out`.
12
- *
13
- * Compound-body aware: a body with multiple colliders has multiple BVH
14
- * leaves (one per collider). Each leaf's AABB query may turn up the same
15
- * other-body multiple times (across the multiple leaves on either side)
16
- * but the per-slot touched flag collapses those into one entry in the
17
- * canonical `(min, max)` pair list — narrowphase then resolves the
18
- * collider-cross-product internally.
19
- *
20
- * Determinism: pair encounter order is fully determined by the awake-list
21
- * order × per-body collider-list order × BVH leaf traversal order. All
22
- * three are deterministic functions of the link/unlink sequence and the
23
- * BVH state, so two worlds with the same scene history produce identical
24
- * pair lists.
25
- *
26
- * @param {BodyStorage} storage
27
- * @param {BVH} dynamic_bvh
28
- * @param {BVH} static_bvh
29
- * @param {ManifoldStore} manifolds
30
- * @param {Array<Array<{collider: Collider, transform: Transform, entity: number, bvhNode: number}>>} body_collider_lists
31
- * @param {PairList} pair_list_out cleared and filled
32
- * @param {((idA: number, idB: number) => boolean) | null} pair_filter optional
33
- * gate. Returning `false` rejects the pair entirely — no manifold
34
- * slot, no narrowphase, no contact events. Existing cached
35
- * manifolds for a rejected pair time out naturally via the
36
- * manifold-store grace counter.
37
- */
38
- export function generate_pairs(
39
- storage,
40
- dynamic_bvh,
41
- static_bvh,
42
- manifolds,
43
- body_collider_lists,
44
- pair_list_out,
45
- pair_filter = null
46
- ) {
47
- pair_list_out.clear();
48
-
49
- const awake_count = storage.awake_count;
50
-
51
- for (let i = 0; i < awake_count; i++) {
52
- const body_idx = storage.awake_at(i);
53
- const list = body_collider_lists[body_idx];
54
- if (list === undefined || list.length === 0) continue;
55
-
56
- const gen = storage.generation_at(body_idx);
57
- const my_packed = (body_idx << 8) | gen;
58
-
59
- for (let k = 0; k < list.length; k++) {
60
- const node = list[k].bvhNode;
61
- dynamic_bvh.node_get_aabb(node, scratch_aabb);
62
-
63
- // Dynamic ↔ Dynamic.
64
- let n = bvh_query_user_data_overlaps_aabb(
65
- candidates, 0,
66
- dynamic_bvh,
67
- scratch_aabb
68
- );
69
- // The BVH query writes leaves unconditionally — at capacity it both
70
- // drops leaves (typed-array OOB writes no-op) AND returns a count
71
- // past the buffer end, so the loop below would read `undefined`
72
- // candidates and build garbage pairs. Guard the buffer size.
73
- assert.lessThan(n, candidates.length, 'generate_pairs: dynamic broadphase overflowed the candidate buffer');
74
- for (let c = 0; c < n; c++) {
75
- const other = candidates[c];
76
- if (other === my_packed) continue;
77
-
78
- const idA = my_packed < other ? my_packed : other;
79
- const idB = my_packed < other ? other : my_packed;
80
-
81
- const existing = manifolds.find(idA, idB);
82
- if (existing !== -1 && manifolds.is_touched(existing)) continue;
83
- if (pair_filter !== null && !pair_filter(idA, idB)) continue;
84
- manifolds.acquire(idA, idB);
85
- pair_list_out.push(idA, idB);
86
- }
87
-
88
- // Dynamic ↔ Static.
89
- n = bvh_query_user_data_overlaps_aabb(
90
- candidates, 0,
91
- static_bvh,
92
- scratch_aabb
93
- );
94
- assert.lessThan(n, candidates.length, 'generate_pairs: static broadphase overflowed the candidate buffer');
95
- for (let c = 0; c < n; c++) {
96
- const other = candidates[c];
97
- const idA = my_packed < other ? my_packed : other;
98
- const idB = my_packed < other ? other : my_packed;
99
-
100
- const existing = manifolds.find(idA, idB);
101
- if (existing !== -1 && manifolds.is_touched(existing)) continue;
102
- if (pair_filter !== null && !pair_filter(idA, idB)) continue;
103
- manifolds.acquire(idA, idB);
104
- pair_list_out.push(idA, idB);
105
- }
106
- }
107
- }
108
- }
1
+ import { assert } from "../../../core/assert.js";
2
+ import { bvh_query_user_data_overlaps_aabb } from "../../../core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js";
3
+
4
+ const scratch_aabb = new Float64Array(6);
5
+ const candidates = new Uint32Array(1024);
6
+
7
+ /**
8
+ * Iterate every awake body, query both BVHs from each of its attached
9
+ * collider leaves, dedupe via the {@link ManifoldStore}, optionally gate
10
+ * each candidate through a user-supplied filter, and append each surviving
11
+ * body-pair to `pair_list_out`.
12
+ *
13
+ * Compound-body aware: a body with multiple colliders has multiple BVH
14
+ * leaves (one per collider). Each leaf's AABB query may turn up the same
15
+ * other-body multiple times (across the multiple leaves on either side)
16
+ * but the per-slot touched flag collapses those into one entry in the
17
+ * canonical `(min, max)` pair list — narrowphase then resolves the
18
+ * collider-cross-product internally.
19
+ *
20
+ * Determinism: pair encounter order is fully determined by the awake-list
21
+ * order × per-body collider-list order × BVH leaf traversal order. All
22
+ * three are deterministic functions of the link/unlink sequence and the
23
+ * BVH state, so two worlds with the same scene history produce identical
24
+ * pair lists.
25
+ *
26
+ * @param {BodyStorage} storage
27
+ * @param {BVH} dynamic_bvh
28
+ * @param {BVH} static_bvh
29
+ * @param {ManifoldStore} manifolds
30
+ * @param {Array<Array<{collider: Collider, transform: Transform, entity: number, bvhNode: number}>>} body_collider_lists
31
+ * @param {PairList} pair_list_out cleared and filled
32
+ * @param {((idA: number, idB: number) => boolean) | null} pair_filter optional
33
+ * gate. Returning `false` rejects the pair entirely — no manifold
34
+ * slot, no narrowphase, no contact events. Existing cached
35
+ * manifolds for a rejected pair time out naturally via the
36
+ * manifold-store grace counter.
37
+ * @param {*} [pair_filter_this_arg]
38
+ */
39
+ export function generate_pairs(
40
+ storage,
41
+ dynamic_bvh,
42
+ static_bvh,
43
+ manifolds,
44
+ body_collider_lists,
45
+ pair_list_out,
46
+ pair_filter = null,
47
+ pair_filter_this_arg = null,
48
+ ) {
49
+ pair_list_out.clear();
50
+
51
+ const awake_count = storage.awake_count;
52
+
53
+ for (let i = 0; i < awake_count; i++) {
54
+ const body_idx = storage.awake_at(i);
55
+ const list = body_collider_lists[body_idx];
56
+ if (list === undefined || list.length === 0) continue;
57
+
58
+ const gen = storage.generation_at(body_idx);
59
+ const my_packed = (body_idx << 8) | gen;
60
+
61
+ for (let k = 0; k < list.length; k++) {
62
+ const node = list[k].bvhNode;
63
+ dynamic_bvh.node_get_aabb(node, scratch_aabb);
64
+
65
+ // Dynamic ↔ Dynamic.
66
+ let n = bvh_query_user_data_overlaps_aabb(
67
+ candidates, 0,
68
+ dynamic_bvh,
69
+ scratch_aabb
70
+ );
71
+ // The BVH query writes leaves unconditionally at capacity it both
72
+ // drops leaves (typed-array OOB writes no-op) AND returns a count
73
+ // past the buffer end, so the loop below would read `undefined`
74
+ // candidates and build garbage pairs. Guard the buffer size.
75
+ assert.lessThan(n, candidates.length, 'generate_pairs: dynamic broadphase overflowed the candidate buffer');
76
+ for (let c = 0; c < n; c++) {
77
+ const other = candidates[c];
78
+ if (other === my_packed) continue;
79
+
80
+ const idA = my_packed < other ? my_packed : other;
81
+ const idB = my_packed < other ? other : my_packed;
82
+
83
+ const existing = manifolds.find(idA, idB);
84
+ if (existing !== -1 && manifolds.is_touched(existing)) continue;
85
+ if (pair_filter !== null && !pair_filter.call(pair_filter_this_arg, idA, idB)) continue;
86
+ manifolds.acquire(idA, idB);
87
+ pair_list_out.push(idA, idB);
88
+ }
89
+
90
+ // Dynamic ↔ Static.
91
+ n = bvh_query_user_data_overlaps_aabb(
92
+ candidates, 0,
93
+ static_bvh,
94
+ scratch_aabb
95
+ );
96
+ assert.lessThan(n, candidates.length, 'generate_pairs: static broadphase overflowed the candidate buffer');
97
+ for (let c = 0; c < n; c++) {
98
+ const other = candidates[c];
99
+ const idA = my_packed < other ? my_packed : other;
100
+ const idB = my_packed < other ? other : my_packed;
101
+
102
+ const existing = manifolds.find(idA, idB);
103
+ if (existing !== -1 && manifolds.is_touched(existing)) continue;
104
+ if (pair_filter !== null && !pair_filter.call(pair_filter_this_arg, idA, idB)) continue;
105
+ manifolds.acquire(idA, idB);
106
+ pair_list_out.push(idA, idB);
107
+ }
108
+ }
109
+ }
110
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"solve_constraints.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/constraint/solve_constraints.js"],"names":[],"mappings":"AA+MA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,sCAHW,MAAM,MAAa,MAAM,MAAa,MAAM,MAAa,MAAM,OAC/D,YAAY,QAkCtB;AA0JD;;;;;;;;;;;;;GAaG;AACH,qCANW,OAAO,iCAEP,MAAM,SAEN,MAAM,QAsPhB"}
1
+ {"version":3,"file":"solve_constraints.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/constraint/solve_constraints.js"],"names":[],"mappings":"AAiOA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,sCAHW,MAAM,MAAa,MAAM,MAAa,MAAM,MAAa,MAAM,OAC/D,YAAY,QAkCtB;AA0JD;;;;;;;;;;;;;GAaG;AACH,qCANW,OAAO,iCAEP,MAAM,SAEN,MAAM,QAsPhB"}