@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.
- package/package.json +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.d.ts +23 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.js +295 -0
- package/src/engine/graphics/GraphicsEngine.d.ts.map +1 -1
- package/src/engine/graphics/GraphicsEngine.js +18 -8
- package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map +1 -1
- package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js +8 -5
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.js +18 -10
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOUpscaleShader.js +1 -1
- package/src/engine/navigation/mesh/NavigationMesh.d.ts +6 -2
- package/src/engine/navigation/mesh/NavigationMesh.d.ts.map +1 -1
- package/src/engine/navigation/mesh/NavigationMesh.js +234 -212
- package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts +7 -3
- package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts.map +1 -1
- package/src/engine/navigation/mesh/bt_mesh_face_find_path.js +67 -73
- package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts +16 -5
- package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts.map +1 -1
- package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.js +262 -147
- package/src/engine/navigation/mesh/build/navmesh_build_topology.d.ts.map +1 -1
- package/src/engine/navigation/mesh/build/navmesh_build_topology.js +33 -3
- package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts +4 -1
- package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts.map +1 -1
- package/src/engine/navigation/mesh/bvh_query_nearest_face.js +164 -131
- package/src/engine/physics/broadphase/generate_pairs.js +110 -110
- 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
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* @param {
|
|
31
|
-
* @param {
|
|
32
|
-
* @param {number}
|
|
33
|
-
* @param {number}
|
|
34
|
-
*
|
|
35
|
-
* @param {number} [
|
|
36
|
-
* @
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
+
}
|