@woosh/meep-engine 2.163.2 → 2.163.3

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 (47) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.d.ts +12 -0
  3. package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.d.ts.map +1 -0
  4. package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.js +18 -0
  5. package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.d.ts +14 -0
  6. package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.d.ts.map +1 -0
  7. package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.js +24 -0
  8. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.d.ts.map +1 -1
  9. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +368 -290
  10. package/src/core/geom/vec3/v3_uniform_sample_cone.d.ts +11 -0
  11. package/src/core/geom/vec3/v3_uniform_sample_cone.d.ts.map +1 -0
  12. package/src/core/geom/vec3/v3_uniform_sample_cone.js +21 -0
  13. package/src/core/math/physics/brdf/cone_cosine_from_roughness.d.ts +13 -0
  14. package/src/core/math/physics/brdf/cone_cosine_from_roughness.d.ts.map +1 -0
  15. package/src/core/math/physics/brdf/cone_cosine_from_roughness.js +28 -0
  16. package/src/core/math/physics/brdf/reflection_sample_weight.d.ts +18 -0
  17. package/src/core/math/physics/brdf/reflection_sample_weight.d.ts.map +1 -0
  18. package/src/core/math/physics/brdf/reflection_sample_weight.js +48 -0
  19. package/src/engine/graphics/GraphicsEngine.d.ts.map +1 -1
  20. package/src/engine/graphics/GraphicsEngine.js +52 -0
  21. package/src/engine/graphics/sh3/sky/hosek/make_environment_sky_hosek.d.ts +26 -0
  22. package/src/engine/graphics/sh3/sky/hosek/make_environment_sky_hosek.d.ts.map +1 -0
  23. package/src/engine/graphics/sh3/sky/hosek/make_environment_sky_hosek.js +49 -0
  24. package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.d.ts +26 -0
  25. package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.d.ts.map +1 -0
  26. package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.js +70 -0
  27. package/src/engine/graphics/sh3/sky/hosek/setup_environment_sky_from_ecd.d.ts +24 -0
  28. package/src/engine/graphics/sh3/sky/hosek/setup_environment_sky_from_ecd.d.ts.map +1 -0
  29. package/src/engine/graphics/sh3/sky/hosek/setup_environment_sky_from_ecd.js +51 -0
  30. package/src/engine/graphics/texture/EnvironmentTextureProjection.d.ts +9 -0
  31. package/src/engine/graphics/texture/EnvironmentTextureProjection.d.ts.map +1 -0
  32. package/src/engine/graphics/texture/EnvironmentTextureProjection.js +15 -0
  33. package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.d.ts +44 -0
  34. package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.d.ts.map +1 -0
  35. package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.js +189 -0
  36. package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.d.ts +25 -0
  37. package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.d.ts.map +1 -0
  38. package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.js +51 -0
  39. package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.d.ts +15 -0
  40. package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.d.ts.map +1 -0
  41. package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.js +63 -0
  42. package/src/engine/navigation/mesh/build/bt_mesh_carve_height_clearance.d.ts +27 -0
  43. package/src/engine/navigation/mesh/build/bt_mesh_carve_height_clearance.d.ts.map +1 -0
  44. package/src/engine/navigation/mesh/build/bt_mesh_carve_height_clearance.js +323 -0
  45. package/src/engine/navigation/mesh/build/navmesh_build_topology.d.ts.map +1 -1
  46. package/src/engine/navigation/mesh/build/navmesh_build_topology.js +223 -226
  47. package/src/engine/.fuse_hidden0000001500000001 +0 -581
@@ -1,226 +1,223 @@
1
- import { assert } from "../../../../core/assert.js";
2
- import { BVH } from "../../../../core/bvh2/bvh3/BVH.js";
3
- import { BinaryTopology } from "../../../../core/geom/3d/topology/struct/binary/BinaryTopology.js";
4
- import {
5
- bt_mesh_cleanup_faceless_references
6
- } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_cleanup_faceless_references.js";
7
- import {
8
- bt_mesh_compact
9
- } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_compact.js";
10
- import {
11
- bt_mesh_compute_face_normals
12
- } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js";
13
-
14
- import {
15
- bt_mesh_face_decouple_islands
16
- } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple_islands.js";
17
- import {
18
- bt_mesh_face_island_erode
19
- } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js";
20
- import {
21
- bt_mesh_from_unindexed_geometry
22
- } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_from_unindexed_geometry.js";
23
- import { bt_mesh_triangulate } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_triangulate.js";
24
- import {
25
- bt_mesh_fuse_duplicate_edges
26
- } from "../../../../core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js";
27
- import {
28
- bt_merge_verts_by_distance
29
- } from "../../../../core/geom/3d/topology/struct/binary/io/vertex/bt_merge_verts_by_distance.js";
30
- import { bt_face_is_degenerate } from "../../../../core/geom/3d/topology/struct/binary/query/bt_face_is_degenerate.js";
31
- import {
32
- bt_mesh_bridge_islands
33
- } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.js";
34
- import {
35
- bt_mesh_compute_face_islands
36
- } from "../../../../core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js";
37
- import { v3_angle_between } from "../../../../core/geom/vec3/v3_angle_between.js";
38
- import Vector3 from "../../../../core/geom/Vector3.js";
39
- import { bvh_build_from_bt_mesh } from "../bvh_build_from_bt_mesh.js";
40
- import { bvh_build_from_unindexed_triangles } from "./bvh_build_from_unindexed_triangles.js";
41
- import { enforce_agent_height_clearance } from "./enforce_agent_height_clearance.js";
42
-
43
-
44
- /**
45
- * Build from given scene geometry
46
- * @param {BinaryTopology} destination
47
- * @param {BinaryTopology} source Must be triangulated. Use {@link bt_mesh_triangulate} if needed.
48
- * @param {number} [agent_radius]
49
- * @param {number} [agent_height]
50
- * @param {number} [agent_max_step_height] agent can bridge vertical gaps in topology, such as stepping up a stair
51
- * @param {number} [agent_max_step_distance] agent can bridge lateral gaps in topology, such as stepping over a hole in the floor
52
- * @param {number} [agent_max_climb_angle] In radians, how steep of an angle can the agent go up by
53
- * @param {Vector3} [up] Defines world's "UP" direction, this is what the agent will respect for climbing constraint
54
- */
55
- export function navmesh_build_topology({
56
- destination,
57
- source,
58
- agent_radius = 0,
59
- agent_height = 2,
60
- agent_max_step_height = 0,
61
- agent_max_step_distance = 0,
62
- agent_max_climb_angle = Math.PI / 4,
63
- up = Vector3.up,
64
- }) {
65
-
66
- assert.defined(destination, 'destination');
67
- assert.defined(source, 'source');
68
-
69
- assert.greaterThanOrEqual(agent_radius, 0, 'agent_radius');
70
- assert.greaterThanOrEqual(agent_height, 0, 'agent_height');
71
- assert.greaterThanOrEqual(agent_max_climb_angle, 0, 'agent_max_climb_angle');
72
- assert.greaterThanOrEqual(agent_max_step_height, 0, 'agent_max_step_height');
73
- assert.greaterThanOrEqual(agent_max_step_distance, 0, 'agent_max_step_distance');
74
-
75
- // The walkable-angle filter below reads each source face's normal. Compute them defensively rather
76
- // than relying on an undocumented "caller already populated normals" precondition - normals are
77
- // derived data, so this is idempotent. NOTE: a single face normal cannot distinguish a correctly
78
- // wound floor from a flipped one (or a ceiling), so the source is still required to have consistent
79
- // outward winding; that remains an input contract we cannot recover from here.
80
- bt_mesh_compute_face_normals(source);
81
-
82
- const source_bvh = new BVH();
83
-
84
- // prepare a BVH, we'll need it for height queries later on
85
- bvh_build_from_bt_mesh(source_bvh, source);
86
-
87
- const scratch_normal = new Float32Array(3);
88
-
89
- // unpack topology into triangle soup
90
- const source_face_count = source.faces.size;
91
-
92
- // TODO add cleanup phase
93
- // - fuse vertices of tiny triangles eliminating them
94
- // - identify flat areas and re-triangulate them to remove unnecessary triangles
95
-
96
- /**
97
- *
98
- * @type {number[]}
99
- */
100
- const raw_triangles = [];
101
- let triangle_count = 0;
102
-
103
- for (let face_id = 0; face_id < source_face_count; face_id++) {
104
-
105
- source.face_read_normal(scratch_normal, 0, face_id);
106
-
107
- const angle = v3_angle_between(up.x, up.y, up.z, scratch_normal[0], scratch_normal[1], scratch_normal[2]);
108
-
109
- if (angle > agent_max_climb_angle) {
110
- // discard
111
- continue;
112
- }
113
-
114
- if (bt_face_is_degenerate(source, face_id)) {
115
- // discard
116
- continue;
117
- }
118
-
119
- let loop = source.face_read_loop(face_id);
120
- const loop_start = loop;
121
-
122
- let face_vertex_count = 0;
123
-
124
- do {
125
-
126
- assert.lessThan(face_vertex_count, 3, 'not a triangle');
127
-
128
- const vertex = source.loop_read_vertex(loop);
129
-
130
- const triangle_offset = triangle_count * 9;
131
-
132
- source.vertex_read_coordinate(raw_triangles, triangle_offset + face_vertex_count * 3, vertex);
133
-
134
- face_vertex_count++;
135
-
136
- loop = source.loop_read_next(loop);
137
-
138
- } while (loop !== loop_start)
139
-
140
- triangle_count++;
141
- }
142
-
143
- // knock out holes in triangles based on agent's height
144
- if (agent_height > 0) {
145
-
146
- triangle_count = enforce_agent_height_clearance({
147
- bvh: source_bvh,
148
- source: source,
149
- agent_height: agent_height,
150
- agent_radius: agent_radius,
151
- triangle_count: triangle_count,
152
- triangles: raw_triangles,
153
- up: up,
154
- });
155
-
156
- }
157
-
158
- {
159
- const bvh = new BVH();
160
-
161
- // first dump all triangles into a BVH for speed of access
162
- bvh_build_from_unindexed_triangles(bvh, raw_triangles, triangle_count);
163
-
164
- // find possible triangle edge connections, that is - where edges of 2 triangles touch, but don't match exactly.
165
- // the touching edges will need to be cut and affected triangles split accordingly
166
-
167
- const mesh = new BinaryTopology();
168
-
169
- // raw_triangles may have stale tail data from filtering steps above (e.g. height clearance);
170
- // bt_mesh_from_unindexed_geometry reads the whole array, so trim to the live range first.
171
- raw_triangles.length = triangle_count * 9;
172
-
173
- bt_mesh_from_unindexed_geometry(mesh, raw_triangles);
174
-
175
- // fuse coincident positions so neighbouring triangles share a vertex id...
176
- bt_merge_verts_by_distance(mesh, 1e-6);
177
-
178
- // ...then fuse the resulting duplicate edges so they share an edge id too,
179
- // which is what the loop-based neighbour queries rely on
180
- bt_mesh_fuse_duplicate_edges(mesh);
181
-
182
- const mesh_bounds = new Float32Array(6);
183
- bvh.node_get_aabb(bvh.root, mesh_bounds);
184
-
185
- const islands = bt_mesh_compute_face_islands(mesh);
186
-
187
- // enabled us to modify islands independently
188
- bt_mesh_face_decouple_islands(mesh, islands);
189
-
190
- // shrink islands to agent_radius
191
- for (const island of islands) {
192
- bt_mesh_face_island_erode(mesh, island, agent_radius);
193
- }
194
-
195
- // TODO attempt to reduce mesh complexity by re-triangulating flat areas or running a very constrained decimation
196
-
197
- // remove dangling references
198
- bt_mesh_cleanup_faceless_references(mesh);
199
-
200
- // Island erosion (above) kills faces via the pool's free-list, leaving holes in the ID space.
201
- // `faces.size` is a high-water mark, not a live count, so consumers that iterate `0..faces.size`
202
- // (e.g. bvh_build_from_bt_mesh) would touch freed slots. Compact so every consumer sees a dense,
203
- // hole-free topology - this is the single invariant downstream code relies on.
204
- bt_mesh_compact(mesh);
205
-
206
- // bridge across small steps / gaps so stair-separated or slightly-broken tiers are reachable.
207
- // Opt-in: with both step params 0 (the default) the topology is left untouched.
208
- if (agent_max_step_height > 0 || agent_max_step_distance > 0) {
209
- bt_mesh_bridge_islands(mesh, {
210
- max_step_height: agent_max_step_height,
211
- max_step_distance: agent_max_step_distance,
212
- up,
213
- });
214
-
215
- // bridging rebuilds via vertex-merge / edge-fuse, which can leave holes; re-densify
216
- bt_mesh_compact(mesh);
217
- }
218
-
219
- // face normals are consumed by navigation queries (string-pulling portal normals), populate them now
220
- bt_mesh_compute_face_normals(mesh);
221
-
222
- // write out to destination
223
- destination.copy(mesh);
224
- }
225
-
226
- }
1
+ import { assert } from "../../../../core/assert.js";
2
+ import { BVH } from "../../../../core/bvh2/bvh3/BVH.js";
3
+ import { BinaryTopology } from "../../../../core/geom/3d/topology/struct/binary/BinaryTopology.js";
4
+ import {
5
+ bt_mesh_cleanup_faceless_references
6
+ } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_cleanup_faceless_references.js";
7
+ import {
8
+ bt_mesh_compact
9
+ } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_compact.js";
10
+ import {
11
+ bt_mesh_compute_face_normals
12
+ } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js";
13
+
14
+ import {
15
+ bt_mesh_face_decouple_islands
16
+ } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple_islands.js";
17
+ import {
18
+ bt_mesh_face_island_erode
19
+ } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js";
20
+ import {
21
+ bt_mesh_from_unindexed_geometry
22
+ } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_from_unindexed_geometry.js";
23
+ import { bt_mesh_triangulate } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_triangulate.js";
24
+ import {
25
+ bt_mesh_fuse_duplicate_edges
26
+ } from "../../../../core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js";
27
+ import {
28
+ bt_merge_verts_by_distance
29
+ } from "../../../../core/geom/3d/topology/struct/binary/io/vertex/bt_merge_verts_by_distance.js";
30
+ import { bt_face_is_degenerate } from "../../../../core/geom/3d/topology/struct/binary/query/bt_face_is_degenerate.js";
31
+ import {
32
+ bt_mesh_bridge_islands
33
+ } from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.js";
34
+ import {
35
+ bt_mesh_compute_face_islands
36
+ } from "../../../../core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js";
37
+ import { v3_angle_between } from "../../../../core/geom/vec3/v3_angle_between.js";
38
+ import Vector3 from "../../../../core/geom/Vector3.js";
39
+ import { bvh_build_from_bt_mesh } from "../bvh_build_from_bt_mesh.js";
40
+ import { bvh_build_from_unindexed_triangles } from "./bvh_build_from_unindexed_triangles.js";
41
+ import { bt_mesh_carve_height_clearance } from "./bt_mesh_carve_height_clearance.js";
42
+
43
+
44
+ /**
45
+ * Build from given scene geometry
46
+ * @param {BinaryTopology} destination
47
+ * @param {BinaryTopology} source Must be triangulated. Use {@link bt_mesh_triangulate} if needed.
48
+ * @param {number} [agent_radius]
49
+ * @param {number} [agent_height]
50
+ * @param {number} [agent_max_step_height] agent can bridge vertical gaps in topology, such as stepping up a stair
51
+ * @param {number} [agent_max_step_distance] agent can bridge lateral gaps in topology, such as stepping over a hole in the floor
52
+ * @param {number} [agent_max_climb_angle] In radians, how steep of an angle can the agent go up by
53
+ * @param {Vector3} [up] Defines world's "UP" direction, this is what the agent will respect for climbing constraint
54
+ */
55
+ export function navmesh_build_topology({
56
+ destination,
57
+ source,
58
+ agent_radius = 0,
59
+ agent_height = 2,
60
+ agent_max_step_height = 0,
61
+ agent_max_step_distance = 0,
62
+ agent_max_climb_angle = Math.PI / 4,
63
+ up = Vector3.up,
64
+ }) {
65
+
66
+ assert.defined(destination, 'destination');
67
+ assert.defined(source, 'source');
68
+
69
+ assert.greaterThanOrEqual(agent_radius, 0, 'agent_radius');
70
+ assert.greaterThanOrEqual(agent_height, 0, 'agent_height');
71
+ assert.greaterThanOrEqual(agent_max_climb_angle, 0, 'agent_max_climb_angle');
72
+ assert.greaterThanOrEqual(agent_max_step_height, 0, 'agent_max_step_height');
73
+ assert.greaterThanOrEqual(agent_max_step_distance, 0, 'agent_max_step_distance');
74
+
75
+ // The walkable-angle filter below reads each source face's normal. Compute them defensively rather
76
+ // than relying on an undocumented "caller already populated normals" precondition - normals are
77
+ // derived data, so this is idempotent. NOTE: a single face normal cannot distinguish a correctly
78
+ // wound floor from a flipped one (or a ceiling), so the source is still required to have consistent
79
+ // outward winding; that remains an input contract we cannot recover from here.
80
+ bt_mesh_compute_face_normals(source);
81
+
82
+ const source_bvh = new BVH();
83
+
84
+ // prepare a BVH, we'll need it for height queries later on
85
+ bvh_build_from_bt_mesh(source_bvh, source);
86
+
87
+ const scratch_normal = new Float32Array(3);
88
+
89
+ // unpack topology into triangle soup
90
+ const source_face_count = source.faces.size;
91
+
92
+ // TODO add cleanup phase
93
+ // - fuse vertices of tiny triangles eliminating them
94
+ // - identify flat areas and re-triangulate them to remove unnecessary triangles
95
+
96
+ /**
97
+ *
98
+ * @type {number[]}
99
+ */
100
+ const raw_triangles = [];
101
+ let triangle_count = 0;
102
+
103
+ for (let face_id = 0; face_id < source_face_count; face_id++) {
104
+
105
+ source.face_read_normal(scratch_normal, 0, face_id);
106
+
107
+ const angle = v3_angle_between(up.x, up.y, up.z, scratch_normal[0], scratch_normal[1], scratch_normal[2]);
108
+
109
+ if (angle > agent_max_climb_angle) {
110
+ // discard
111
+ continue;
112
+ }
113
+
114
+ if (bt_face_is_degenerate(source, face_id)) {
115
+ // discard
116
+ continue;
117
+ }
118
+
119
+ let loop = source.face_read_loop(face_id);
120
+ const loop_start = loop;
121
+
122
+ let face_vertex_count = 0;
123
+
124
+ do {
125
+
126
+ assert.lessThan(face_vertex_count, 3, 'not a triangle');
127
+
128
+ const vertex = source.loop_read_vertex(loop);
129
+
130
+ const triangle_offset = triangle_count * 9;
131
+
132
+ source.vertex_read_coordinate(raw_triangles, triangle_offset + face_vertex_count * 3, vertex);
133
+
134
+ face_vertex_count++;
135
+
136
+ loop = source.loop_read_next(loop);
137
+
138
+ } while (loop !== loop_start)
139
+
140
+ triangle_count++;
141
+ }
142
+
143
+ {
144
+ const bvh = new BVH();
145
+
146
+ // first dump all triangles into a BVH for speed of access
147
+ bvh_build_from_unindexed_triangles(bvh, raw_triangles, triangle_count);
148
+
149
+ // find possible triangle edge connections, that is - where edges of 2 triangles touch, but don't match exactly.
150
+ // the touching edges will need to be cut and affected triangles split accordingly
151
+
152
+ const mesh = new BinaryTopology();
153
+
154
+ // raw_triangles may have stale tail data from filtering steps above (e.g. degenerate/steep);
155
+ // bt_mesh_from_unindexed_geometry reads the whole array, so trim to the live range first.
156
+ raw_triangles.length = triangle_count * 9;
157
+
158
+ bt_mesh_from_unindexed_geometry(mesh, raw_triangles);
159
+
160
+ // fuse coincident positions so neighbouring triangles share a vertex id...
161
+ bt_merge_verts_by_distance(mesh, 1e-6);
162
+
163
+ // ...then fuse the resulting duplicate edges so they share an edge id too,
164
+ // which is what the loop-based neighbour queries rely on
165
+ bt_mesh_fuse_duplicate_edges(mesh);
166
+
167
+ // Carve holes wherever an agent of the given height would not fit under overhead geometry.
168
+ // Done on the welded topology (rather than the raw soup) so the subdivision is conformal -
169
+ // bt_edge_split re-triangulates every face around a shared edge, so neighbours can never crack
170
+ // apart - and adaptive, hugging each obstacle's contour instead of tessellating whole triangles.
171
+ if (agent_height > 0) {
172
+ bt_mesh_carve_height_clearance({
173
+ mesh,
174
+ source,
175
+ source_bvh,
176
+ agent_height,
177
+ agent_radius,
178
+ up,
179
+ });
180
+ }
181
+
182
+ const islands = bt_mesh_compute_face_islands(mesh);
183
+
184
+ // enabled us to modify islands independently
185
+ bt_mesh_face_decouple_islands(mesh, islands);
186
+
187
+ // shrink islands to agent_radius
188
+ for (const island of islands) {
189
+ bt_mesh_face_island_erode(mesh, island, agent_radius);
190
+ }
191
+
192
+ // TODO attempt to reduce mesh complexity by re-triangulating flat areas or running a very constrained decimation
193
+
194
+ // remove dangling references
195
+ bt_mesh_cleanup_faceless_references(mesh);
196
+
197
+ // Island erosion (above) kills faces via the pool's free-list, leaving holes in the ID space.
198
+ // `faces.size` is a high-water mark, not a live count, so consumers that iterate `0..faces.size`
199
+ // (e.g. bvh_build_from_bt_mesh) would touch freed slots. Compact so every consumer sees a dense,
200
+ // hole-free topology - this is the single invariant downstream code relies on.
201
+ bt_mesh_compact(mesh);
202
+
203
+ // bridge across small steps / gaps so stair-separated or slightly-broken tiers are reachable.
204
+ // Opt-in: with both step params 0 (the default) the topology is left untouched.
205
+ if (agent_max_step_height > 0 || agent_max_step_distance > 0) {
206
+ bt_mesh_bridge_islands(mesh, {
207
+ max_step_height: agent_max_step_height,
208
+ max_step_distance: agent_max_step_distance,
209
+ up,
210
+ });
211
+
212
+ // bridging rebuilds via vertex-merge / edge-fuse, which can leave holes; re-densify
213
+ bt_mesh_compact(mesh);
214
+ }
215
+
216
+ // face normals are consumed by navigation queries (string-pulling portal normals), populate them now
217
+ bt_mesh_compute_face_normals(mesh);
218
+
219
+ // write out to destination
220
+ destination.copy(mesh);
221
+ }
222
+
223
+ }