@woosh/meep-engine 2.148.0 → 2.150.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 (26) 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/engine/graphics/GraphicsEngine.d.ts.map +1 -1
  6. package/src/engine/graphics/GraphicsEngine.js +18 -8
  7. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map +1 -1
  8. package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js +8 -5
  9. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.js +18 -10
  10. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOUpscaleShader.js +1 -1
  11. package/src/engine/navigation/mesh/NavigationMesh.d.ts +6 -2
  12. package/src/engine/navigation/mesh/NavigationMesh.d.ts.map +1 -1
  13. package/src/engine/navigation/mesh/NavigationMesh.js +234 -212
  14. package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts +7 -3
  15. package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts.map +1 -1
  16. package/src/engine/navigation/mesh/bt_mesh_face_find_path.js +67 -73
  17. package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts +16 -5
  18. package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts.map +1 -1
  19. package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.js +262 -147
  20. package/src/engine/navigation/mesh/build/navmesh_build_topology.d.ts.map +1 -1
  21. package/src/engine/navigation/mesh/build/navmesh_build_topology.js +33 -3
  22. package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts +4 -1
  23. package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts.map +1 -1
  24. package/src/engine/navigation/mesh/bvh_query_nearest_face.js +164 -131
  25. package/src/engine/physics/broadphase/generate_pairs.js +110 -110
  26. package/src/engine/physics/queries/raycast.js +201 -201
@@ -1,131 +1,164 @@
1
- import { assert } from "../../../core/assert.js";
2
- import {
3
- COLUMN_CHILD_1,
4
- COLUMN_CHILD_2,
5
- COLUMN_USER_DATA,
6
- ELEMENT_WORD_COUNT,
7
- NULL_NODE
8
- } from "../../../core/bvh2/bvh3/BVH.js";
9
- import { SCRATCH_UINT32_TRAVERSAL_STACK } from "../../../core/collection/SCRATCH_UINT32_TRAVERSAL_STACK.js";
10
- import {
11
- aabb3_unsigned_distance_sqr_to_point
12
- } from "../../../core/geom/3d/aabb/aabb3_unsigned_distance_sqr_to_point.js";
13
- import { NULL_POINTER } from "../../../core/geom/3d/topology/struct/binary/BinaryTopology.js";
14
- import {
15
- computeTriangleClosestPointToPointBarycentric
16
- } from "../../../core/geom/3d/triangle/computeTriangleClosestPointToPointBarycentric.js";
17
-
18
- const stack = SCRATCH_UINT32_TRAVERSAL_STACK;
19
-
20
- const scratch_barycentric = new Float32Array(2);
21
- const scratch_coord_a = new Float32Array(3);
22
- const scratch_coord_b = new Float32Array(3);
23
- const scratch_coord_c = new Float32Array(3);
24
-
25
- /**
26
- * Find the face of `mesh` whose surface is closest to the given point.
27
- * Expects `bvh` to have been built by {@link bvh_build_from_bt_mesh}, so each leaf's user data is a face ID.
28
- * Faces are expected to be triangles (as produced by the navmesh build pipeline).
29
- *
30
- * @param {BVH} bvh
31
- * @param {BinaryTopology} mesh
32
- * @param {number} x
33
- * @param {number} y
34
- * @param {number} z
35
- * @param {number} [max_distance=Infinity] optional cutoff, only faces within this distance are considered
36
- * @returns {number} face ID of the nearest face, or {@link NULL_POINTER} if no face was found within the cutoff
37
- */
38
- export function bvh_query_nearest_face(bvh, mesh, x, y, z, max_distance = Infinity) {
39
- assert.defined(bvh, "bvh");
40
- assert.defined(mesh, "mesh");
41
- assert.equal(mesh.isBinaryTopology, true, "mesh.isBinaryTopology !== true");
42
-
43
- const root = bvh.root;
44
-
45
- if (root === NULL_NODE) {
46
- return NULL_POINTER;
47
- }
48
-
49
- const stack_top = stack.pointer++;
50
-
51
- stack[stack_top] = root;
52
-
53
- let best_face = NULL_POINTER;
54
- let best_distance_sqr = max_distance * max_distance;
55
-
56
- const float32 = bvh.__data_float32;
57
- const uint32 = bvh.__data_uint32;
58
-
59
- do {
60
- stack.pointer--;
61
-
62
- const node = stack[stack.pointer];
63
- const address = node * ELEMENT_WORD_COUNT;
64
-
65
- // lower bound on distance via AABB
66
- const aabb_distance_sqr = aabb3_unsigned_distance_sqr_to_point(
67
- float32[address], float32[address + 1], float32[address + 2],
68
- float32[address + 3], float32[address + 4], float32[address + 5],
69
- x, y, z
70
- );
71
-
72
- if (aabb_distance_sqr >= best_distance_sqr) {
73
- // this subtree cannot contain a closer face
74
- continue;
75
- }
76
-
77
- const child_1 = uint32[address + COLUMN_CHILD_1];
78
-
79
- if (child_1 !== NULL_NODE) {
80
- // internal node, descend into both children
81
- stack[stack.pointer++] = child_1;
82
- stack[stack.pointer++] = uint32[address + COLUMN_CHILD_2];
83
- continue;
84
- }
85
-
86
- // leaf node: refine with actual triangle distance
87
- const face_id = uint32[address + COLUMN_USER_DATA];
88
-
89
- const loop_a = mesh.face_read_loop(face_id);
90
-
91
- if (loop_a === NULL_POINTER) {
92
- continue;
93
- }
94
-
95
- const loop_b = mesh.loop_read_next(loop_a);
96
- const loop_c = mesh.loop_read_next(loop_b);
97
-
98
- mesh.vertex_read_coordinate(scratch_coord_a, 0, mesh.loop_read_vertex(loop_a));
99
- mesh.vertex_read_coordinate(scratch_coord_b, 0, mesh.loop_read_vertex(loop_b));
100
- mesh.vertex_read_coordinate(scratch_coord_c, 0, mesh.loop_read_vertex(loop_c));
101
-
102
- computeTriangleClosestPointToPointBarycentric(
103
- scratch_barycentric, 0,
104
- x, y, z,
105
- scratch_coord_a[0], scratch_coord_a[1], scratch_coord_a[2],
106
- scratch_coord_b[0], scratch_coord_b[1], scratch_coord_b[2],
107
- scratch_coord_c[0], scratch_coord_c[1], scratch_coord_c[2]
108
- );
109
-
110
- const weight_a = scratch_barycentric[0];
111
- const weight_b = scratch_barycentric[1];
112
- const weight_c = 1 - weight_a - weight_b;
113
-
114
- const contact_x = scratch_coord_a[0] * weight_a + scratch_coord_b[0] * weight_b + scratch_coord_c[0] * weight_c;
115
- const contact_y = scratch_coord_a[1] * weight_a + scratch_coord_b[1] * weight_b + scratch_coord_c[1] * weight_c;
116
- const contact_z = scratch_coord_a[2] * weight_a + scratch_coord_b[2] * weight_b + scratch_coord_c[2] * weight_c;
117
-
118
- const dx = contact_x - x;
119
- const dy = contact_y - y;
120
- const dz = contact_z - z;
121
-
122
- const triangle_distance_sqr = dx * dx + dy * dy + dz * dz;
123
-
124
- if (triangle_distance_sqr < best_distance_sqr) {
125
- best_distance_sqr = triangle_distance_sqr;
126
- best_face = face_id;
127
- }
128
- } while (stack.pointer > stack_top);
129
-
130
- return best_face;
131
- }
1
+ import { assert } from "../../../core/assert.js";
2
+ import {
3
+ COLUMN_CHILD_1,
4
+ COLUMN_CHILD_2,
5
+ COLUMN_USER_DATA,
6
+ ELEMENT_WORD_COUNT,
7
+ NULL_NODE
8
+ } from "../../../core/bvh2/bvh3/BVH.js";
9
+ import { SCRATCH_UINT32_TRAVERSAL_STACK } from "../../../core/collection/SCRATCH_UINT32_TRAVERSAL_STACK.js";
10
+ import {
11
+ aabb3_unsigned_distance_sqr_to_point
12
+ } from "../../../core/geom/3d/aabb/aabb3_unsigned_distance_sqr_to_point.js";
13
+ import { NULL_POINTER } from "../../../core/geom/3d/topology/struct/binary/BinaryTopology.js";
14
+ import {
15
+ computeTriangleClosestPointToPointBarycentric
16
+ } from "../../../core/geom/3d/triangle/computeTriangleClosestPointToPointBarycentric.js";
17
+
18
+ const stack = SCRATCH_UINT32_TRAVERSAL_STACK;
19
+
20
+ // single reusable coordinate scratch; callers unpack into locals between the calls that fill it
21
+ const scratch_coords = new Float32Array(3);
22
+
23
+ /**
24
+ * Find the face of `mesh` whose surface is closest to the given point.
25
+ * Expects `bvh` to have been built by {@link bvh_build_from_bt_mesh}, so each leaf's user data is a face ID.
26
+ * Faces are expected to be triangles (as produced by the navmesh build pipeline).
27
+ *
28
+ * @param {BVH} bvh
29
+ * @param {BinaryTopology} mesh
30
+ * @param {number} x
31
+ * @param {number} y
32
+ * @param {number} z
33
+ * @param {Float32Array|number[]} out_point the closest point ON the winning face's surface (the snapped
34
+ * contact point) is written here when a face is found. Required, must not be null.
35
+ * @param {number} [out_point_offset=0] offset into `out_point` to write the snapped XYZ triple
36
+ * @param {number} [max_distance=Infinity] optional cutoff, only faces within this distance are considered
37
+ * @returns {number} face ID of the nearest face, or {@link NULL_POINTER} if no face was found within the cutoff
38
+ */
39
+ export function bvh_query_nearest_face(
40
+ bvh,
41
+ mesh,
42
+ x, y, z,
43
+ out_point,
44
+ out_point_offset = 0,
45
+ max_distance = Infinity
46
+ ) {
47
+
48
+ assert.defined(bvh, "bvh");
49
+ assert.defined(mesh, "mesh");
50
+ assert.notNull(out_point, "out_point");
51
+ assert.defined(out_point, "out_point");
52
+ assert.equal(mesh.isBinaryTopology, true, "mesh.isBinaryTopology !== true");
53
+
54
+ const root = bvh.root;
55
+
56
+ if (root === NULL_NODE) {
57
+ return NULL_POINTER;
58
+ }
59
+
60
+ const stack_top = stack.pointer++;
61
+
62
+ stack[stack_top] = root;
63
+
64
+ let best_face = NULL_POINTER;
65
+ let best_distance_sqr = max_distance * max_distance;
66
+
67
+ // closest point on the winning face's surface, tracked so callers can snap a query point onto the mesh
68
+ let best_contact_x = 0;
69
+ let best_contact_y = 0;
70
+ let best_contact_z = 0;
71
+
72
+ const float32 = bvh.__data_float32;
73
+ const uint32 = bvh.__data_uint32;
74
+
75
+ do {
76
+ stack.pointer--;
77
+
78
+ const node = stack[stack.pointer];
79
+ const address = node * ELEMENT_WORD_COUNT;
80
+
81
+ // lower bound on distance via AABB
82
+ const aabb_distance_sqr = aabb3_unsigned_distance_sqr_to_point(
83
+ float32[address], float32[address + 1], float32[address + 2],
84
+ float32[address + 3], float32[address + 4], float32[address + 5],
85
+ x, y, z
86
+ );
87
+
88
+ if (aabb_distance_sqr >= best_distance_sqr) {
89
+ // this subtree cannot contain a closer face
90
+ continue;
91
+ }
92
+
93
+ const child_1 = uint32[address + COLUMN_CHILD_1];
94
+
95
+ if (child_1 !== NULL_NODE) {
96
+ // internal node, descend into both children
97
+ stack[stack.pointer++] = child_1;
98
+ stack[stack.pointer++] = uint32[address + COLUMN_CHILD_2];
99
+ continue;
100
+ }
101
+
102
+ // leaf node: refine with actual triangle distance
103
+ const face_id = uint32[address + COLUMN_USER_DATA];
104
+
105
+ const loop_a = mesh.face_read_loop(face_id);
106
+
107
+ if (loop_a === NULL_POINTER) {
108
+ continue;
109
+ }
110
+
111
+ const loop_b = mesh.loop_read_next(loop_a);
112
+ const loop_c = mesh.loop_read_next(loop_b);
113
+
114
+ // read each vertex into the shared scratch, unpacking into locals before the next read overwrites it
115
+ mesh.vertex_read_coordinate(scratch_coords, 0, mesh.loop_read_vertex(loop_a));
116
+ const ax = scratch_coords[0], ay = scratch_coords[1], az = scratch_coords[2];
117
+
118
+ mesh.vertex_read_coordinate(scratch_coords, 0, mesh.loop_read_vertex(loop_b));
119
+ const bx = scratch_coords[0], by = scratch_coords[1], bz = scratch_coords[2];
120
+
121
+ mesh.vertex_read_coordinate(scratch_coords, 0, mesh.loop_read_vertex(loop_c));
122
+ const cx = scratch_coords[0], cy = scratch_coords[1], cz = scratch_coords[2];
123
+
124
+ // reuse the same scratch for the barycentric output (only the first two slots are written)
125
+ computeTriangleClosestPointToPointBarycentric(
126
+ scratch_coords, 0,
127
+ x, y, z,
128
+ ax, ay, az,
129
+ bx, by, bz,
130
+ cx, cy, cz
131
+ );
132
+
133
+ const weight_a = scratch_coords[0];
134
+ const weight_b = scratch_coords[1];
135
+ const weight_c = 1 - weight_a - weight_b;
136
+
137
+ const contact_x = ax * weight_a + bx * weight_b + cx * weight_c;
138
+ const contact_y = ay * weight_a + by * weight_b + cy * weight_c;
139
+ const contact_z = az * weight_a + bz * weight_b + cz * weight_c;
140
+
141
+ const dx = contact_x - x;
142
+ const dy = contact_y - y;
143
+ const dz = contact_z - z;
144
+
145
+ const triangle_distance_sqr = dx * dx + dy * dy + dz * dz;
146
+
147
+ if (triangle_distance_sqr < best_distance_sqr) {
148
+ best_distance_sqr = triangle_distance_sqr;
149
+ best_face = face_id;
150
+
151
+ best_contact_x = contact_x;
152
+ best_contact_y = contact_y;
153
+ best_contact_z = contact_z;
154
+ }
155
+ } while (stack.pointer > stack_top);
156
+
157
+ if (best_face !== NULL_POINTER) {
158
+ out_point[out_point_offset] = best_contact_x;
159
+ out_point[out_point_offset + 1] = best_contact_y;
160
+ out_point[out_point_offset + 2] = best_contact_z;
161
+ }
162
+
163
+ return best_face;
164
+ }
@@ -1,110 +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
- * @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
+ 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
+ }