@woosh/meep-engine 2.163.6 → 2.163.8
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/2d/line/line_segment_intersection_fraction_2d.d.ts +21 -0
- package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.d.ts.map +1 -0
- package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.js +42 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.d.ts +2 -2
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +120 -179
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.d.ts +9 -10
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js +20 -14
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.d.ts +17 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.js +45 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.d.ts +40 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.js +84 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js +53 -78
- package/src/core/geom/vec3/v3_matrix3_rotate.d.ts +16 -0
- package/src/core/geom/vec3/v3_matrix3_rotate.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_matrix3_rotate.js +49 -0
- package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.d.ts +2 -2
- package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js +46 -46
- package/src/engine/graphics/sh3/path_tracer/sampling/getBiasedNormalSample.d.ts.map +1 -1
- package/src/engine/graphics/sh3/path_tracer/sampling/getBiasedNormalSample.js +6 -28
- package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +185 -0
- package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts +11 -0
- 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 +623 -100
- package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts +11 -0
- package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts.map +1 -0
- package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.js +472 -0
- package/src/engine/navigation/mesh/build/navmesh_build_topology.d.ts.map +1 -1
- package/src/engine/navigation/mesh/build/navmesh_build_topology.js +36 -39
- package/src/engine/navigation/mesh/navmesh_polyanya_find_path.d.ts +17 -0
- package/src/engine/navigation/mesh/navmesh_polyanya_find_path.d.ts.map +1 -0
- package/src/engine/navigation/mesh/navmesh_polyanya_find_path.js +613 -0
- package/src/engine/navigation/mesh/build/bt_mesh_carve_height_clearance.d.ts +0 -28
- package/src/engine/navigation/mesh/build/bt_mesh_carve_height_clearance.d.ts.map +0 -1
- package/src/engine/navigation/mesh/build/bt_mesh_carve_height_clearance.js +0 -358
- package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts +0 -23
- package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts.map +0 -1
- package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.js +0 -319
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
import { assert } from "../../../../core/assert.js";
|
|
2
|
-
import { NULL_NODE } from "../../../../core/bvh2/bvh3/BVH.js";
|
|
3
|
-
import {
|
|
4
|
-
bvh_query_leaves_ray_segment
|
|
5
|
-
} from "../../../../core/bvh2/bvh3/query/bvh_query_leaves_ray_segment.js";
|
|
6
|
-
import { NULL_POINTER } from "../../../../core/geom/3d/topology/struct/binary/BinaryTopology.js";
|
|
7
|
-
import { compute_triangle_area_3d } from "../../../../core/geom/3d/triangle/compute_triangle_area_3d.js";
|
|
8
|
-
import {
|
|
9
|
-
computeTriangleRayIntersectionBarycentric
|
|
10
|
-
} from "../../../../core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js";
|
|
11
|
-
import { clamp } from "../../../../core/math/clamp.js";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Upper bound on the per-edge subdivision factor, so a single huge triangle cannot explode into an
|
|
15
|
-
* unbounded number of sub-triangles. At this cap a triangle is sampled with up to K_MAX^2 points.
|
|
16
|
-
* @type {number}
|
|
17
|
-
*/
|
|
18
|
-
const K_MAX = 64;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Small offset (in world units) used to lift a ray origin off the surface it sits on, so the upward
|
|
22
|
-
* clearance ray does not immediately re-intersect the floor it starts from.
|
|
23
|
-
* @type {number}
|
|
24
|
-
*/
|
|
25
|
-
const SURFACE_EPSILON = 1e-4;
|
|
26
|
-
|
|
27
|
-
// reused scratch (build-time, but there's no reason to churn allocations per triangle)
|
|
28
|
-
const ray_leaf_buffer = [];
|
|
29
|
-
const intersection_result = new Float32Array(6);
|
|
30
|
-
const tri_a = new Float32Array(3);
|
|
31
|
-
const tri_b = new Float32Array(3);
|
|
32
|
-
const tri_c = new Float32Array(3);
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* True if there is no source geometry within `agent_height` directly above the point, measured along
|
|
36
|
-
* the world up direction. Casts a ray up from the point (nudged off the surface) and checks every
|
|
37
|
-
* source face whose AABB the ray crosses with a precise ray-triangle test.
|
|
38
|
-
*
|
|
39
|
-
* @param {BVH} bvh source-geometry BVH (leaves carry source face IDs)
|
|
40
|
-
* @param {BinaryTopology} source source mesh, used to resolve face IDs to triangle vertices
|
|
41
|
-
* @param {number} px
|
|
42
|
-
* @param {number} py
|
|
43
|
-
* @param {number} pz
|
|
44
|
-
* @param {number} up_x normalized world up
|
|
45
|
-
* @param {number} up_y
|
|
46
|
-
* @param {number} up_z
|
|
47
|
-
* @param {number} agent_height
|
|
48
|
-
* @returns {boolean}
|
|
49
|
-
*/
|
|
50
|
-
function point_has_clearance(bvh, source, px, py, pz, up_x, up_y, up_z, agent_height) {
|
|
51
|
-
const origin_x = px + up_x * SURFACE_EPSILON;
|
|
52
|
-
const origin_y = py + up_y * SURFACE_EPSILON;
|
|
53
|
-
const origin_z = pz + up_z * SURFACE_EPSILON;
|
|
54
|
-
|
|
55
|
-
const leaf_count = bvh_query_leaves_ray_segment(
|
|
56
|
-
bvh, bvh.root,
|
|
57
|
-
ray_leaf_buffer, 0,
|
|
58
|
-
origin_x, origin_y, origin_z,
|
|
59
|
-
up_x, up_y, up_z,
|
|
60
|
-
0, agent_height
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
for (let i = 0; i < leaf_count; i++) {
|
|
64
|
-
const node = ray_leaf_buffer[i];
|
|
65
|
-
const face_id = bvh.node_get_user_data(node);
|
|
66
|
-
|
|
67
|
-
const loop_a = source.face_read_loop(face_id);
|
|
68
|
-
|
|
69
|
-
if (loop_a === NULL_POINTER) {
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const loop_b = source.loop_read_next(loop_a);
|
|
74
|
-
const loop_c = source.loop_read_next(loop_b);
|
|
75
|
-
|
|
76
|
-
source.vertex_read_coordinate(tri_a, 0, source.loop_read_vertex(loop_a));
|
|
77
|
-
source.vertex_read_coordinate(tri_b, 0, source.loop_read_vertex(loop_b));
|
|
78
|
-
source.vertex_read_coordinate(tri_c, 0, source.loop_read_vertex(loop_c));
|
|
79
|
-
|
|
80
|
-
const hit = computeTriangleRayIntersectionBarycentric(
|
|
81
|
-
intersection_result,
|
|
82
|
-
origin_x, origin_y, origin_z,
|
|
83
|
-
up_x, up_y, up_z,
|
|
84
|
-
tri_a[0], tri_a[1], tri_a[2],
|
|
85
|
-
tri_b[0], tri_b[1], tri_b[2],
|
|
86
|
-
tri_c[0], tri_c[1], tri_c[2]
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
if (!hit) {
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const t = intersection_result[0];
|
|
94
|
-
|
|
95
|
-
if (t > 0 && t <= agent_height) {
|
|
96
|
-
// an overhead obstruction sits within the agent's height
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Append a triangle (9 floats) to `output`.
|
|
106
|
-
*/
|
|
107
|
-
function emit_triangle(
|
|
108
|
-
output,
|
|
109
|
-
ax, ay, az,
|
|
110
|
-
bx, by, bz,
|
|
111
|
-
cx, cy, cz
|
|
112
|
-
) {
|
|
113
|
-
output.push(ax, ay, az, bx, by, bz, cx, cy, cz);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Knock holes in the walkable triangle soup wherever an agent of the given height would not fit under
|
|
118
|
-
* overhead geometry.
|
|
119
|
-
*
|
|
120
|
-
* Approach: subdivide each candidate triangle to a resolution tied to the agent's footprint, cast an
|
|
121
|
-
* upward clearance ray from each sub-triangle centroid against the source geometry, and keep only the
|
|
122
|
-
* sub-triangles that have clearance. Fully-clear triangles are emitted unchanged (no subdivision), so
|
|
123
|
-
* the common case adds no triangles; fully-blocked triangles are dropped; partially-blocked triangles
|
|
124
|
-
* are replaced by their clear sub-triangles. Accuracy is bounded by the sampling resolution.
|
|
125
|
-
*
|
|
126
|
-
* The `triangles` array is rewritten in place with the surviving triangles.
|
|
127
|
-
*
|
|
128
|
-
* @param {BVH} bvh source-geometry BVH (leaves carry source face IDs)
|
|
129
|
-
* @param {BinaryTopology} source source mesh, used to resolve face IDs to triangle vertices
|
|
130
|
-
* @param {number} agent_height
|
|
131
|
-
* @param {number} agent_radius
|
|
132
|
-
* @param {number} triangle_count number of triangles currently in `triangles`
|
|
133
|
-
* @param {number[]} triangles flat XYZ soup, 9 floats per triangle
|
|
134
|
-
* @param {Vector3} up world up direction
|
|
135
|
-
* @returns {number} new triangle count
|
|
136
|
-
*/
|
|
137
|
-
export function enforce_agent_height_clearance({
|
|
138
|
-
bvh,
|
|
139
|
-
source,
|
|
140
|
-
agent_height,
|
|
141
|
-
agent_radius,
|
|
142
|
-
triangle_count,
|
|
143
|
-
triangles,
|
|
144
|
-
up,
|
|
145
|
-
}) {
|
|
146
|
-
|
|
147
|
-
assert.defined(bvh, 'bvh');
|
|
148
|
-
assert.defined(source, 'source');
|
|
149
|
-
assert.greaterThan(agent_height, 0, 'agent_height');
|
|
150
|
-
|
|
151
|
-
if (bvh.root === NULL_NODE) {
|
|
152
|
-
// no source geometry to obstruct anything
|
|
153
|
-
return triangle_count;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// normalize up so ray-intersection distances are world distances
|
|
157
|
-
let up_x = up.x;
|
|
158
|
-
let up_y = up.y;
|
|
159
|
-
let up_z = up.z;
|
|
160
|
-
|
|
161
|
-
const up_length = Math.sqrt(up_x * up_x + up_y * up_y + up_z * up_z);
|
|
162
|
-
|
|
163
|
-
assert.greaterThan(up_length, 0, 'up vector length');
|
|
164
|
-
|
|
165
|
-
up_x /= up_length;
|
|
166
|
-
up_y /= up_length;
|
|
167
|
-
up_z /= up_length;
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Resolution at which clearance is sampled. Tied to the agent footprint so the sampling grid is
|
|
171
|
-
* fine enough to resolve gaps the agent could (or could not) fit through.
|
|
172
|
-
* @type {number}
|
|
173
|
-
*/
|
|
174
|
-
const sampling_resolution = Math.min(
|
|
175
|
-
agent_height / 4,
|
|
176
|
-
Math.max(agent_radius / 2, 0.001)
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
assert.isFinite(sampling_resolution, 'sampling_resolution');
|
|
180
|
-
assert.greaterThan(sampling_resolution, 0, 'sampling_resolution');
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Surviving triangles. Built separately because the count can grow (subdivision), then copied back.
|
|
184
|
-
* @type {number[]}
|
|
185
|
-
*/
|
|
186
|
-
const output = [];
|
|
187
|
-
|
|
188
|
-
for (let i = 0; i < triangle_count; i++) {
|
|
189
|
-
const address = i * 9;
|
|
190
|
-
|
|
191
|
-
const ax = triangles[address];
|
|
192
|
-
const ay = triangles[address + 1];
|
|
193
|
-
const az = triangles[address + 2];
|
|
194
|
-
|
|
195
|
-
const bx = triangles[address + 3];
|
|
196
|
-
const by = triangles[address + 4];
|
|
197
|
-
const bz = triangles[address + 5];
|
|
198
|
-
|
|
199
|
-
const cx = triangles[address + 6];
|
|
200
|
-
const cy = triangles[address + 7];
|
|
201
|
-
const cz = triangles[address + 8];
|
|
202
|
-
|
|
203
|
-
const area = compute_triangle_area_3d(ax, ay, az, bx, by, bz, cx, cy, cz);
|
|
204
|
-
|
|
205
|
-
if (area === 0) {
|
|
206
|
-
// degenerate, nothing meaningful to sample - keep as-is
|
|
207
|
-
emit_triangle(output, ax, ay, az, bx, by, bz, cx, cy, cz);
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// edges, used both to size the subdivision and to place sub-triangle vertices
|
|
212
|
-
const e0x = bx - ax, e0y = by - ay, e0z = bz - az; // a -> b
|
|
213
|
-
const e1x = cx - ax, e1y = cy - ay, e1z = cz - az; // a -> c
|
|
214
|
-
|
|
215
|
-
const len_ab = Math.sqrt(e0x * e0x + e0y * e0y + e0z * e0z);
|
|
216
|
-
const len_ac = Math.sqrt(e1x * e1x + e1y * e1y + e1z * e1z);
|
|
217
|
-
const dbcx = cx - bx, dbcy = cy - by, dbcz = cz - bz;
|
|
218
|
-
const len_bc = Math.sqrt(dbcx * dbcx + dbcy * dbcy + dbcz * dbcz);
|
|
219
|
-
|
|
220
|
-
const longest_edge = Math.max(len_ab, len_ac, len_bc);
|
|
221
|
-
|
|
222
|
-
const k = clamp(Math.ceil(longest_edge / sampling_resolution), 1, K_MAX);
|
|
223
|
-
|
|
224
|
-
const inv_k = 1 / k;
|
|
225
|
-
|
|
226
|
-
// Sub-triangle vertices are P(i,j) = a + (i/k)*(b-a) + (j/k)*(c-a), for i+j <= k.
|
|
227
|
-
// Walk the k^2 sub-triangles (k*(k+... )/...) testing each centroid for clearance.
|
|
228
|
-
let any_blocked = false;
|
|
229
|
-
let any_clear = false;
|
|
230
|
-
|
|
231
|
-
// reuse a per-triangle scratch list of clear sub-triangles only when we actually subdivide
|
|
232
|
-
const clear_subtriangles = [];
|
|
233
|
-
|
|
234
|
-
for (let row = 0; row < k; row++) {
|
|
235
|
-
for (let col = 0; col < k - row; col++) {
|
|
236
|
-
// "upward" sub-triangle: P(col,row), P(col+1,row), P(col,row+1)
|
|
237
|
-
emit_subtriangle_if_clear(
|
|
238
|
-
bvh, source, agent_height, up_x, up_y, up_z,
|
|
239
|
-
ax, ay, az, e0x, e0y, e0z, e1x, e1y, e1z, inv_k,
|
|
240
|
-
col, row, col + 1, row, col, row + 1,
|
|
241
|
-
clear_subtriangles
|
|
242
|
-
) ? (any_clear = true) : (any_blocked = true);
|
|
243
|
-
|
|
244
|
-
// "downward" sub-triangle: P(col+1,row), P(col+1,row+1), P(col,row+1)
|
|
245
|
-
if (col < k - row - 1) {
|
|
246
|
-
emit_subtriangle_if_clear(
|
|
247
|
-
bvh, source, agent_height, up_x, up_y, up_z,
|
|
248
|
-
ax, ay, az, e0x, e0y, e0z, e1x, e1y, e1z, inv_k,
|
|
249
|
-
col + 1, row, col + 1, row + 1, col, row + 1,
|
|
250
|
-
clear_subtriangles
|
|
251
|
-
) ? (any_clear = true) : (any_blocked = true);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (!any_blocked) {
|
|
257
|
-
// fully clear - keep the original triangle, no subdivision, no bloat
|
|
258
|
-
emit_triangle(output, ax, ay, az, bx, by, bz, cx, cy, cz);
|
|
259
|
-
} else if (any_clear) {
|
|
260
|
-
// partially blocked - replace with the surviving sub-triangles
|
|
261
|
-
for (let s = 0; s < clear_subtriangles.length; s++) {
|
|
262
|
-
output.push(clear_subtriangles[s]);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
// else fully blocked - drop the triangle entirely
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// rewrite the soup in place with the survivors
|
|
269
|
-
for (let i = 0; i < output.length; i++) {
|
|
270
|
-
triangles[i] = output[i];
|
|
271
|
-
}
|
|
272
|
-
triangles.length = output.length;
|
|
273
|
-
|
|
274
|
-
return (output.length / 9) | 0;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Build sub-triangle vertices P(i,j) = a + (i/k)*e0 + (j/k)*e1, test its centroid for clearance, and
|
|
279
|
-
* push its 9 coordinates into `out` when clear. Returns whether it was clear.
|
|
280
|
-
*/
|
|
281
|
-
function emit_subtriangle_if_clear(
|
|
282
|
-
bvh, source, agent_height, up_x, up_y, up_z,
|
|
283
|
-
ax, ay, az, e0x, e0y, e0z, e1x, e1y, e1z, inv_k,
|
|
284
|
-
i0, j0, i1, j1, i2, j2,
|
|
285
|
-
out
|
|
286
|
-
) {
|
|
287
|
-
const s0 = i0 * inv_k, t0 = j0 * inv_k;
|
|
288
|
-
const s1 = i1 * inv_k, t1 = j1 * inv_k;
|
|
289
|
-
const s2 = i2 * inv_k, t2 = j2 * inv_k;
|
|
290
|
-
|
|
291
|
-
const p0x = ax + s0 * e0x + t0 * e1x;
|
|
292
|
-
const p0y = ay + s0 * e0y + t0 * e1y;
|
|
293
|
-
const p0z = az + s0 * e0z + t0 * e1z;
|
|
294
|
-
|
|
295
|
-
const p1x = ax + s1 * e0x + t1 * e1x;
|
|
296
|
-
const p1y = ay + s1 * e0y + t1 * e1y;
|
|
297
|
-
const p1z = az + s1 * e0z + t1 * e1z;
|
|
298
|
-
|
|
299
|
-
const p2x = ax + s2 * e0x + t2 * e1x;
|
|
300
|
-
const p2y = ay + s2 * e0y + t2 * e1y;
|
|
301
|
-
const p2z = az + s2 * e0z + t2 * e1z;
|
|
302
|
-
|
|
303
|
-
const centroid_x = (p0x + p1x + p2x) / 3;
|
|
304
|
-
const centroid_y = (p0y + p1y + p2y) / 3;
|
|
305
|
-
const centroid_z = (p0z + p1z + p2z) / 3;
|
|
306
|
-
|
|
307
|
-
const clear = point_has_clearance(
|
|
308
|
-
bvh, source,
|
|
309
|
-
centroid_x, centroid_y, centroid_z,
|
|
310
|
-
up_x, up_y, up_z,
|
|
311
|
-
agent_height
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
if (clear) {
|
|
315
|
-
out.push(p0x, p0y, p0z, p1x, p1y, p1z, p2x, p2y, p2z);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return clear;
|
|
319
|
-
}
|