@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
@@ -8,19 +8,24 @@ import {
8
8
  const open = new Uint32Heap();
9
9
 
10
10
  /**
11
- *
12
11
  * @type {Set<number>}
13
12
  */
14
13
  const closed = new Set();
15
14
 
16
15
  /**
17
- *
16
+ * Least known traversal cost (sum of centroid-to-centroid distances) from start to each face.
18
17
  * @type {Map<number, number>}
19
18
  */
20
19
  const g_score = new Map();
21
20
 
22
21
  /**
23
- * Note that we limit the supported number of neighbors, a reasonable meshes will fit this criteria
22
+ * Reconstruction parent pointers: face -> the face we reached it from on the best path.
23
+ * @type {Map<number, number>}
24
+ */
25
+ const came_from = new Map();
26
+
27
+ /**
28
+ * Note that we limit the supported number of neighbors, a reasonable mesh will fit this criteria
24
29
  * @type {Uint32Array}
25
30
  */
26
31
  const neighbors = new Uint32Array(256);
@@ -28,96 +33,80 @@ const neighbors = new Uint32Array(256);
28
33
  const scratch_array_f32 = new Float32Array(6);
29
34
 
30
35
  /**
36
+ * Straight-line distance between the centroids of two faces.
37
+ *
38
+ * This is used both as the step cost (between adjacent faces) and as the heuristic (from a face to
39
+ * the goal). Because, by the triangle inequality, the straight-line distance from any face to the
40
+ * goal is never greater than the summed centroid-to-centroid distance of an actual face path between
41
+ * them, the heuristic is admissible AND consistent. A* therefore returns a shortest-distance corridor
42
+ * (under the centroid metric) rather than a fewest-faces one.
31
43
  *
32
44
  * @param {number} a
33
45
  * @param {number} b
34
46
  * @param {BinaryTopology} topology
35
47
  * @returns {number}
36
48
  */
37
- function heuristic(a, b, topology) {
38
- // compare centroid distances
49
+ function face_centroid_distance(a, b, topology) {
39
50
  bt_face_get_centroid(scratch_array_f32, 0, topology, a);
40
51
  bt_face_get_centroid(scratch_array_f32, 3, topology, b);
41
52
 
42
- // delta
43
53
  const dx = scratch_array_f32[0] - scratch_array_f32[3];
44
54
  const dy = scratch_array_f32[1] - scratch_array_f32[4];
45
55
  const dz = scratch_array_f32[2] - scratch_array_f32[5];
46
56
 
47
- // distance
48
- return dx * dx + dy * dy + dz * dz;
57
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
49
58
  }
50
59
 
51
60
  /**
61
+ * Walk parent pointers from goal back to start, writing the face sequence into `output` in
62
+ * START -> GOAL order. Never writes more than `max_path_length` entries: if the corridor is longer
63
+ * than that it does not fit the caller's buffer and 0 is returned (no partial/garbage result is used).
52
64
  *
53
65
  * @param {number[]|Uint32Array} output
54
- * @param {number} goal
55
- * @param {Map<number, number>} g_score
56
- * @param {BinaryTopology} topology
57
- * @returns {number}
66
+ * @param {number} start_face
67
+ * @param {number} goal_face
68
+ * @param {Map<number, number>} came_from
69
+ * @param {number} max_path_length
70
+ * @returns {number} number of faces written, or 0 if the corridor exceeds `max_path_length`
58
71
  */
59
- function construct_path(output, goal, g_score, topology) {
60
-
61
- let current = goal;
62
- let path_length = 0;
72
+ function construct_path(output, start_face, goal_face, came_from, max_path_length) {
73
+ let length = 0;
74
+ let current = goal_face;
63
75
 
64
- // 1. Walk back via lowest g-score path until we get to 0 (start)
76
+ // walk back to the start, writing in reverse (goal -> start)
65
77
  while (true) {
66
- output[path_length++] = current;
67
-
68
- const current_g = g_score.get(current);
69
-
70
- // If we reached the start node (g_score === 0), we're done traversing
71
- if (current_g === 0) {
72
- break;
78
+ if (length >= max_path_length) {
79
+ // corridor does not fit the output buffer; refuse rather than writing out of bounds
80
+ return 0;
73
81
  }
74
82
 
75
- const neighbor_count = bt_face_get_neighbour_faces(neighbors, 0, topology, current);
83
+ output[length] = current;
84
+ length++;
76
85
 
77
- let best_neighbor = -1;
78
- let lowest_g = Infinity;
79
-
80
- // Find the neighbor with the lowest g_score
81
- for (let i = 0; i < neighbor_count; i++) {
82
- const neighbor = neighbors[i];
83
-
84
- if (g_score.has(neighbor)) {
85
- const g = g_score.get(neighbor);
86
-
87
- if (g < lowest_g) {
88
- lowest_g = g;
89
- best_neighbor = neighbor;
90
- }
91
-
92
- }
93
- }
94
-
95
- // Safeguard: Break if no valid neighbor is found or if we aren't strictly descending.
96
- // (Since your traversal_cost is currently 1.0, lowest_g should be current_g - 1.0)
97
- if (best_neighbor === -1 || lowest_g >= current_g) {
86
+ if (current === start_face) {
98
87
  break;
99
88
  }
100
89
 
101
- current = best_neighbor;
90
+ current = came_from.get(current);
102
91
  }
103
92
 
104
- // 2. Reverse the path in-place to get START -> GOAL order
105
- const half_length = Math.floor(path_length / 2);
93
+ // reverse in place to get START -> GOAL order
94
+ const half_length = length >> 1;
106
95
 
107
96
  for (let i = 0; i < half_length; i++) {
108
- const mirror_i = path_length - 1 - i;
97
+ const mirror_i = length - 1 - i;
109
98
  const temp = output[i];
110
99
 
111
100
  output[i] = output[mirror_i];
112
101
  output[mirror_i] = temp;
113
102
  }
114
103
 
115
- return path_length;
104
+ return length;
116
105
  }
117
106
 
118
107
 
119
108
  /**
120
- * Find a path through topology faces.
109
+ * Find a shortest path through topology faces.
121
110
  * If a path is found - the result will contain start and goal faces.
122
111
  *
123
112
  * NOTE: if either start or goal faces are not part of the topology - an empty path will be produced.
@@ -126,13 +115,18 @@ function construct_path(output, goal, g_score, topology) {
126
115
  * @param {number} start_face_id
127
116
  * @param {number} goal_face_id
128
117
  * @param {BinaryTopology} topology
129
- * @returns {number} number of faces that make up the path. This will be 0 if no path is found.
118
+ * @param {number} [max_path_length=Infinity] cap on the number of faces written to `output`. If the
119
+ * shortest corridor is longer than this it does not fit the buffer and 0 is returned. Pass the
120
+ * capacity of `output` to guarantee no out-of-bounds writes.
121
+ * @returns {number} number of faces that make up the path. This will be 0 if no path is found (or the
122
+ * path does not fit `max_path_length`).
130
123
  */
131
124
  export function bt_mesh_face_find_path(
132
125
  output,
133
126
  start_face_id,
134
127
  goal_face_id,
135
128
  topology,
129
+ max_path_length = Infinity,
136
130
  ) {
137
131
  assert.isArrayLike(output, 'output');
138
132
  assert.isNonNegativeInteger(start_face_id, 'start_face_id');
@@ -141,35 +135,35 @@ export function bt_mesh_face_find_path(
141
135
  assert.defined(topology, 'topology');
142
136
  assert.equal(topology.isBinaryTopology, true, 'topology.isBinaryTopology !== true');
143
137
 
144
- // TODO we can switch traversal to edges instead, that way we can get much better distance heuristics
145
- // we don't know where exactly we're going to cross through the triangle, but we know we're going to cross the edge at least, which narrows the domain
146
-
147
138
  open.clear();
148
139
  closed.clear();
149
140
  g_score.clear();
141
+ came_from.clear();
150
142
 
151
143
  g_score.set(start_face_id, 0);
152
144
 
153
- open.insert(start_face_id, heuristic(start_face_id, goal_face_id, topology));
145
+ open.insert(start_face_id, face_centroid_distance(start_face_id, goal_face_id, topology));
154
146
 
155
147
  while (!open.is_empty()) {
156
148
 
157
149
  const current_node = open.pop_min();
158
150
 
159
- // Lazy-push: a node may be in the heap multiple times if its g-score was improved after an earlier
160
- // push. The lowest-score entry is popped first; later popped duplicates land on an already-closed
161
- // node and must be skipped.
151
+ // Lazy deletion: a face may sit in the heap multiple times if its g-score improved after an
152
+ // earlier push. The lowest-f entry is popped first; later popped duplicates land on an
153
+ // already-closed face and must be skipped.
162
154
  if (closed.has(current_node)) {
163
155
  continue;
164
156
  }
165
157
 
166
158
  if (current_node === goal_face_id) {
167
159
  // Reached the goal
168
- return construct_path(output, goal_face_id, g_score, topology);
160
+ return construct_path(output, start_face_id, goal_face_id, came_from, max_path_length);
169
161
  }
170
162
 
171
163
  closed.add(current_node);
172
164
 
165
+ const current_g_score = g_score.get(current_node);
166
+
173
167
  const neighbor_count = bt_face_get_neighbour_faces(neighbors, 0, topology, current_node);
174
168
 
175
169
  for (let i = 0; i < neighbor_count; i++) {
@@ -181,26 +175,26 @@ export function bt_mesh_face_find_path(
181
175
  continue;
182
176
  }
183
177
 
184
- // TODO compute correct traversal cost based on triangle size
185
- const traversal_cost = 1.0;
186
-
187
- const current_g_score = g_score.get(current_node);
178
+ // step cost is the real geometric distance between adjacent face centroids, so the
179
+ // search minimises corridor length rather than the number of triangles crossed
180
+ const traversal_cost = face_centroid_distance(current_node, neighbor, topology);
188
181
 
189
182
  const cost_so_far = current_g_score + traversal_cost;
190
183
 
191
184
  if (!g_score.has(neighbor) || cost_so_far < g_score.get(neighbor)) {
192
- // better path
185
+ // better path to this neighbour
193
186
 
194
187
  g_score.set(neighbor, cost_so_far);
188
+ came_from.set(neighbor, current_node);
195
189
 
196
- const remaining_heuristic = heuristic(neighbor, goal_face_id, topology);
190
+ const remaining_heuristic = face_centroid_distance(neighbor, goal_face_id, topology);
197
191
 
198
- const refined_heuristic = cost_so_far + remaining_heuristic;
192
+ const f_score = cost_so_far + remaining_heuristic;
199
193
 
200
- // Lazy push: always insert. If neighbor was already on the open list, the older (worse-score)
201
- // entry will be skipped when popped (see closed-check at the top of the loop). This avoids
202
- // an O(n) find_index_by_id scan per relaxation.
203
- open.insert(neighbor, refined_heuristic);
194
+ // Lazy push: always insert. If neighbor was already on the open list, the older
195
+ // (worse-f) entry will be skipped when popped (see closed-check at the top of the
196
+ // loop). This avoids an O(n) find_index_by_id scan per relaxation.
197
+ open.insert(neighbor, f_score);
204
198
 
205
199
  }
206
200
 
@@ -210,4 +204,4 @@ export function bt_mesh_face_find_path(
210
204
 
211
205
  // No result found
212
206
  return 0;
213
- }
207
+ }
@@ -1,12 +1,23 @@
1
1
  /**
2
+ * Knock holes in the walkable triangle soup wherever an agent of the given height would not fit under
3
+ * overhead geometry.
2
4
  *
3
- * @param {BVH} bvh
5
+ * Approach: subdivide each candidate triangle to a resolution tied to the agent's footprint, cast an
6
+ * upward clearance ray from each sub-triangle centroid against the source geometry, and keep only the
7
+ * sub-triangles that have clearance. Fully-clear triangles are emitted unchanged (no subdivision), so
8
+ * the common case adds no triangles; fully-blocked triangles are dropped; partially-blocked triangles
9
+ * are replaced by their clear sub-triangles. Accuracy is bounded by the sampling resolution.
10
+ *
11
+ * The `triangles` array is rewritten in place with the surviving triangles.
12
+ *
13
+ * @param {BVH} bvh source-geometry BVH (leaves carry source face IDs)
14
+ * @param {BinaryTopology} source source mesh, used to resolve face IDs to triangle vertices
4
15
  * @param {number} agent_height
5
16
  * @param {number} agent_radius
6
- * @param {number} triangle_count
7
- * @param {number[]} triangles
8
- * @param {function():number} random
17
+ * @param {number} triangle_count number of triangles currently in `triangles`
18
+ * @param {number[]} triangles flat XYZ soup, 9 floats per triangle
19
+ * @param {Vector3} up world up direction
9
20
  * @returns {number} new triangle count
10
21
  */
11
- export function enforce_agent_height_clearance({ bvh, agent_height, agent_radius, triangle_count, triangles, random }: BVH): number;
22
+ export function enforce_agent_height_clearance({ bvh, source, agent_height, agent_radius, triangle_count, triangles, up, }: BVH): number;
12
23
  //# sourceMappingURL=enforce_agent_height_clearance.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"enforce_agent_height_clearance.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/navigation/mesh/build/enforce_agent_height_clearance.js"],"names":[],"mappings":"AA4CA;;;;;;;;;GASG;AACH,6HAFa,MAAM,CAuJlB"}
1
+ {"version":3,"file":"enforce_agent_height_clearance.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/navigation/mesh/build/enforce_agent_height_clearance.js"],"names":[],"mappings":"AAmHA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,kIAFa,MAAM,CA4IlB"}