@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
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {number[]} soup flat XYZ triangle soup (9 floats per triangle)
|
|
3
|
+
* @param {number} triangle_count
|
|
4
|
+
* @param {BinaryTopology} source source mesh (face normals must be populated)
|
|
5
|
+
* @param {number} agent_height
|
|
6
|
+
* @param {number} agent_radius dilation applied to each footprint
|
|
7
|
+
* @param {Vector3} up
|
|
8
|
+
* @returns {number[]} new soup with (dilated) obstacle footprints removed
|
|
9
|
+
*/
|
|
10
|
+
export function clip_soup_against_overhangs(soup: number[], triangle_count: number, source: BinaryTopology, agent_height: number, agent_radius: number, up: Vector3): number[];
|
|
11
|
+
//# sourceMappingURL=clip_soup_against_overhangs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clip_soup_against_overhangs.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/navigation/mesh/build/clip_soup_against_overhangs.js"],"names":[],"mappings":"AA+MA;;;;;;;;GAQG;AACH,kDARW,MAAM,EAAE,kBACR,MAAM,wCAEN,MAAM,gBACN,MAAM,gBAEJ,MAAM,EAAE,CAkOpB"}
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exact obstacle carving at the triangle-soup level, with a faithful agent-radius offset.
|
|
3
|
+
*
|
|
4
|
+
* Each overhead obstacle's footprint is DILATED by the agent radius (Minkowski sum with a disc: edges
|
|
5
|
+
* pushed out by r, convex corners replaced by a rounded arc), and that dilated polygon is subtracted
|
|
6
|
+
* from the walkable triangles. The resulting hole boundary is therefore the true offset of the
|
|
7
|
+
* obstacle - straight along edges, a clean arc at corners - rather than a wavy/mitred contour produced
|
|
8
|
+
* by sampling-then-eroding. (Run AFTER the outer boundary has been eroded by r, so the obstacle holes
|
|
9
|
+
* are not eroded a second time.)
|
|
10
|
+
*
|
|
11
|
+
* Overhead obstacles are the DOWNWARD-facing source faces; a face carves a walkable triangle only where
|
|
12
|
+
* it sits above it, within `agent_height`.
|
|
13
|
+
*
|
|
14
|
+
* The subtraction uses the disjoint half-plane decomposition of a convex-polygon difference:
|
|
15
|
+
* T \ F = U_i ( T ∩ inside(e_0..e_{i-1}) ∩ outside(e_i) )
|
|
16
|
+
* each term convex, the terms disjoint, the union exactly T minus the convex (dilated) footprint F.
|
|
17
|
+
*
|
|
18
|
+
* Everything is data-oriented: clipping happens in the 2D plane perpendicular to `up`, with polygons
|
|
19
|
+
* held as flat `Float64Array`s of (u,v) pairs and a small set of reused scratch buffers. World XYZ is
|
|
20
|
+
* recovered for each surviving vertex by barycentric reconstruction on its (planar) source triangle, so
|
|
21
|
+
* kept pieces stay exactly on the original - possibly tilted - walkable plane. Degenerate triangles are
|
|
22
|
+
* dropped, and triangles that no footprint overlaps are copied straight through without allocation.
|
|
23
|
+
*
|
|
24
|
+
* The footprints are indexed in a BVH (keyed by their (u,v) extent and carve height band), so each soup
|
|
25
|
+
* triangle only tests the obstacles that can actually overlap it - O(m log k) rather than O(m k) for m
|
|
26
|
+
* soup triangles and k obstacles.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { BVH } from "../../../../core/bvh2/bvh3/BVH.js";
|
|
30
|
+
import { bvh_query_user_data_overlaps_aabb } from "../../../../core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js";
|
|
31
|
+
import { v3_dot } from "../../../../core/geom/vec3/v3_dot.js";
|
|
32
|
+
import { v3_orthonormal_matrix_from_normal } from "../../../../core/geom/vec3/v3_orthonormal_matrix_from_normal.js";
|
|
33
|
+
import { convex_hull_monotone_2d } from "../../../../core/geom/2d/convex-hull/convex_hull_monotone_2d.js";
|
|
34
|
+
import { compute_polygon_area_2d } from "../../../../core/geom/2d/compute_polygon_area_2d.js";
|
|
35
|
+
|
|
36
|
+
const POINT_EPS = 1e-9;
|
|
37
|
+
const AREA_EPS = 1e-9;
|
|
38
|
+
const HEIGHT_EPS = 1e-3;
|
|
39
|
+
const CORNER_SEGMENT_ANGLE = Math.PI / 6; // ~30 deg per rounded-corner segment
|
|
40
|
+
|
|
41
|
+
// Vertex coincidence key. Quantize each projected coordinate to QUANTIZE (1mm) and pack the two integer
|
|
42
|
+
// coordinates into one safe-integer key: cheaper than a string and with no per-probe allocation. Each
|
|
43
|
+
// axis occupies a biased 26-bit field, so the key stays unique while |u|,|v| < 2^25 / QUANTIZE
|
|
44
|
+
// (~33,000 units). A 16-bit-per-axis pack would only reach ~3.3 units and silently collide - navmesh
|
|
45
|
+
// coordinates routinely exceed that (e.g. geometry built 10,000 units from the origin). Coincident
|
|
46
|
+
// vertices are the same source vertex projected identically, so they match at any quantization; the
|
|
47
|
+
// precision only needs to keep genuinely distinct obstacle corners apart (the smallest is ~0.3 units).
|
|
48
|
+
const QUANTIZE = 1e3;
|
|
49
|
+
const KEY_BIAS = 1 << 25; // 33,554,432
|
|
50
|
+
const KEY_STRIDE = 1 << 26; // 67,108,864
|
|
51
|
+
|
|
52
|
+
// Reused clip scratch (stride 2: u,v). Grown on demand so a single allocation serves the whole soup.
|
|
53
|
+
let _cur = new Float64Array(64);
|
|
54
|
+
let _tmp = new Float64Array(64);
|
|
55
|
+
let _side = new Float64Array(64);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Ensure `buf` can hold `vertex_count` (u,v) pairs, returning a large-enough buffer (the same one when
|
|
59
|
+
* it already fits). Contents are not preserved - callers grow before writing.
|
|
60
|
+
*
|
|
61
|
+
* @param {Float64Array} buf
|
|
62
|
+
* @param {number} vertex_count
|
|
63
|
+
* @returns {Float64Array}
|
|
64
|
+
*/
|
|
65
|
+
function ensure_capacity(buf, vertex_count) {
|
|
66
|
+
const needed = vertex_count * 2;
|
|
67
|
+
if (buf.length >= needed) return buf;
|
|
68
|
+
let size = buf.length;
|
|
69
|
+
while (size < needed) size *= 2;
|
|
70
|
+
return new Float64Array(size);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Sutherland-Hodgman clip of a 2D polygon against one half-plane of a directed edge - the general-edge
|
|
75
|
+
* analogue of {@link polygon2_clip_axis_halfplane}. The edge passes through (ex, ez) with direction
|
|
76
|
+
* (dx, dz); `keep_left` keeps the side with `cross(dir, P - e) >= 0`, otherwise the right side.
|
|
77
|
+
*
|
|
78
|
+
* @param {Float64Array} in_pts input vertices (stride 2)
|
|
79
|
+
* @param {number} in_n input vertex count
|
|
80
|
+
* @param {Float64Array} out_pts output buffer (stride 2), sized for at least in_n + 1 vertices
|
|
81
|
+
* @param {number} ex
|
|
82
|
+
* @param {number} ez a point on the edge
|
|
83
|
+
* @param {number} dx
|
|
84
|
+
* @param {number} dz the edge direction
|
|
85
|
+
* @param {boolean} keep_left keep the left half-plane when true
|
|
86
|
+
* @returns {number} surviving vertex count
|
|
87
|
+
*/
|
|
88
|
+
function clip_halfplane(in_pts, in_n, out_pts, ex, ez, dx, dz, keep_left) {
|
|
89
|
+
let out_n = 0;
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < in_n; i++) {
|
|
92
|
+
const j = (i + 1) % in_n;
|
|
93
|
+
|
|
94
|
+
const a_u = in_pts[i * 2], a_v = in_pts[i * 2 + 1];
|
|
95
|
+
const b_u = in_pts[j * 2], b_v = in_pts[j * 2 + 1];
|
|
96
|
+
|
|
97
|
+
const sa = dx * (a_v - ez) - dz * (a_u - ex);
|
|
98
|
+
const sb = dx * (b_v - ez) - dz * (b_u - ex);
|
|
99
|
+
|
|
100
|
+
const in_a = keep_left ? (sa >= -POINT_EPS) : (sa <= POINT_EPS);
|
|
101
|
+
const in_b = keep_left ? (sb >= -POINT_EPS) : (sb <= POINT_EPS);
|
|
102
|
+
|
|
103
|
+
if (in_a) {
|
|
104
|
+
out_pts[out_n * 2] = a_u;
|
|
105
|
+
out_pts[out_n * 2 + 1] = a_v;
|
|
106
|
+
out_n++;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (in_a !== in_b) {
|
|
110
|
+
const t = sa / (sa - sb);
|
|
111
|
+
out_pts[out_n * 2] = a_u + t * (b_u - a_u);
|
|
112
|
+
out_pts[out_n * 2 + 1] = a_v + t * (b_v - a_v);
|
|
113
|
+
out_n++;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return out_n;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Outward Minkowski-with-disc offset of a convex CCW polygon by `r`: each edge is pushed out by `r` and
|
|
122
|
+
* each convex corner is replaced by a rounded arc. Input/output are flat (u,v) pairs.
|
|
123
|
+
*
|
|
124
|
+
* @param {Float64Array} pts convex CCW polygon (stride 2)
|
|
125
|
+
* @param {number} n vertex count
|
|
126
|
+
* @param {number} r offset radius (> 0)
|
|
127
|
+
* @returns {Float64Array} the offset polygon (CCW, stride 2)
|
|
128
|
+
*/
|
|
129
|
+
function offset_convex_ccw(pts, n, r) {
|
|
130
|
+
// outward edge normals (right of each CCW edge)
|
|
131
|
+
const enx = new Float64Array(n);
|
|
132
|
+
const enz = new Float64Array(n);
|
|
133
|
+
for (let i = 0; i < n; i++) {
|
|
134
|
+
const j = (i + 1) % n;
|
|
135
|
+
let dx = pts[j * 2] - pts[i * 2];
|
|
136
|
+
let dz = pts[j * 2 + 1] - pts[i * 2 + 1];
|
|
137
|
+
const len = Math.hypot(dx, dz) || 1;
|
|
138
|
+
dx /= len; dz /= len;
|
|
139
|
+
enx[i] = dz; // (dir.v, -dir.u) = right/outward of a CCW edge
|
|
140
|
+
enz[i] = -dx;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const out = [];
|
|
144
|
+
for (let i = 0; i < n; i++) {
|
|
145
|
+
const prev_edge = (i - 1 + n) % n; // edge ending at vertex i
|
|
146
|
+
const next_edge = i; // edge starting at vertex i
|
|
147
|
+
const vu = pts[i * 2], vv = pts[i * 2 + 1];
|
|
148
|
+
|
|
149
|
+
const a1 = Math.atan2(enz[prev_edge], enx[prev_edge]);
|
|
150
|
+
const a2 = Math.atan2(enz[next_edge], enx[next_edge]);
|
|
151
|
+
let da = a2 - a1;
|
|
152
|
+
while (da < 0) da += 2 * Math.PI;
|
|
153
|
+
while (da > 2 * Math.PI) da -= 2 * Math.PI;
|
|
154
|
+
|
|
155
|
+
const steps = Math.max(1, Math.ceil(da / CORNER_SEGMENT_ANGLE));
|
|
156
|
+
for (let s = 0; s <= steps; s++) {
|
|
157
|
+
const a = a1 + da * (s / steps);
|
|
158
|
+
out.push(vu + r * Math.cos(a), vv + r * Math.sin(a));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return Float64Array.from(out);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Subtract convex footprint `fp` from a single convex polygon, appending the convex, disjoint pieces of
|
|
167
|
+
* (polygon \ fp) to `out_pieces` as right-sized `Float64Array`s. `fp` must be CCW (interior on the left
|
|
168
|
+
* of each edge). The polygon's interior remainder (the part inside `fp`) is discarded.
|
|
169
|
+
*
|
|
170
|
+
* @param {Float64Array} poly polygon vertices (stride 2)
|
|
171
|
+
* @param {number} poly_n polygon vertex count
|
|
172
|
+
* @param {Float64Array} fp footprint vertices (stride 2, CCW)
|
|
173
|
+
* @param {number} fp_n footprint vertex count
|
|
174
|
+
* @param {Float64Array[]} out_pieces destination list; surviving outside pieces are pushed
|
|
175
|
+
*/
|
|
176
|
+
function subtract_footprint(poly, poly_n, fp, fp_n, out_pieces) {
|
|
177
|
+
// current = the running intersection with the inside half-planes processed so far
|
|
178
|
+
_cur = ensure_capacity(_cur, poly_n + fp_n);
|
|
179
|
+
_tmp = ensure_capacity(_tmp, poly_n + fp_n);
|
|
180
|
+
_side = ensure_capacity(_side, poly_n + fp_n);
|
|
181
|
+
|
|
182
|
+
let cur = _cur;
|
|
183
|
+
let tmp = _tmp;
|
|
184
|
+
for (let i = 0; i < poly_n * 2; i++) cur[i] = poly[i];
|
|
185
|
+
let cur_n = poly_n;
|
|
186
|
+
|
|
187
|
+
for (let i = 0; i < fp_n; i++) {
|
|
188
|
+
if (cur_n < 3) return; // fully consumed - nothing outside remains
|
|
189
|
+
|
|
190
|
+
const j = (i + 1) % fp_n;
|
|
191
|
+
const ex = fp[i * 2], ez = fp[i * 2 + 1];
|
|
192
|
+
const dx = fp[j * 2] - ex;
|
|
193
|
+
const dz = fp[j * 2 + 1] - ez;
|
|
194
|
+
if (Math.abs(dx) < POINT_EPS && Math.abs(dz) < POINT_EPS) continue; // degenerate edge
|
|
195
|
+
|
|
196
|
+
// piece outside this edge (and inside all earlier edges) leaves the footprint here -> it survives
|
|
197
|
+
const side_n = clip_halfplane(cur, cur_n, _side, ex, ez, dx, dz, false);
|
|
198
|
+
if (side_n >= 3 && Math.abs(compute_polygon_area_2d(_side, side_n)) > AREA_EPS) {
|
|
199
|
+
out_pieces.push(_side.slice(0, side_n * 2));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// keep marching with the part still inside this edge
|
|
203
|
+
cur_n = clip_halfplane(cur, cur_n, tmp, ex, ez, dx, dz, true);
|
|
204
|
+
const swap = cur; cur = tmp; tmp = swap;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @param {number[]} soup flat XYZ triangle soup (9 floats per triangle)
|
|
210
|
+
* @param {number} triangle_count
|
|
211
|
+
* @param {BinaryTopology} source source mesh (face normals must be populated)
|
|
212
|
+
* @param {number} agent_height
|
|
213
|
+
* @param {number} agent_radius dilation applied to each footprint
|
|
214
|
+
* @param {Vector3} up
|
|
215
|
+
* @returns {number[]} new soup with (dilated) obstacle footprints removed
|
|
216
|
+
*/
|
|
217
|
+
export function clip_soup_against_overhangs(soup, triangle_count, source, agent_height, agent_radius, up) {
|
|
218
|
+
let upx = up.x, upy = up.y, upz = up.z;
|
|
219
|
+
const ul = Math.hypot(upx, upy, upz);
|
|
220
|
+
if (ul === 0) return soup.slice(0, triangle_count * 9);
|
|
221
|
+
upx /= ul; upy /= ul; upz /= ul;
|
|
222
|
+
|
|
223
|
+
// orthonormal basis: rows 0,1 span the plane perpendicular to up (our u,v axes), row 2 is up itself
|
|
224
|
+
const basis = new Float64Array(9);
|
|
225
|
+
v3_orthonormal_matrix_from_normal(basis, 0, upx, upy, upz);
|
|
226
|
+
const u_x = basis[0], u_y = basis[1], u_z = basis[2];
|
|
227
|
+
const v_x = basis[3], v_y = basis[4], v_z = basis[5];
|
|
228
|
+
|
|
229
|
+
// collect downward-facing (overhead) source triangles as flat (u,v) triples plus their mean height
|
|
230
|
+
const overhead_uv = []; // stride 6: u0,v0,u1,v1,u2,v2
|
|
231
|
+
const overhead_h = [];
|
|
232
|
+
const fn = new Float32Array(3);
|
|
233
|
+
const fc = new Float32Array(3);
|
|
234
|
+
const face_count = source.faces.size;
|
|
235
|
+
for (let f = 0; f < face_count; f++) {
|
|
236
|
+
if (!source.faces.is_allocated(f)) continue;
|
|
237
|
+
source.face_read_normal(fn, 0, f);
|
|
238
|
+
if (fn[0] * upx + fn[1] * upy + fn[2] * upz >= -HEIGHT_EPS) continue; // not downward-facing
|
|
239
|
+
|
|
240
|
+
let l = source.face_read_loop(f);
|
|
241
|
+
let hsum = 0;
|
|
242
|
+
for (let k = 0; k < 3; k++) {
|
|
243
|
+
source.vertex_read_coordinate(fc, 0, source.loop_read_vertex(l));
|
|
244
|
+
const x = fc[0], y = fc[1], z = fc[2];
|
|
245
|
+
overhead_uv.push(v3_dot(x, y, z, u_x, u_y, u_z), v3_dot(x, y, z, v_x, v_y, v_z));
|
|
246
|
+
hsum += v3_dot(x, y, z, upx, upy, upz);
|
|
247
|
+
l = source.loop_read_next(l);
|
|
248
|
+
}
|
|
249
|
+
overhead_h.push(hsum / 3);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const overhead_count = overhead_h.length;
|
|
253
|
+
if (overhead_count === 0) return soup.slice(0, triangle_count * 9);
|
|
254
|
+
|
|
255
|
+
// Group overhead triangles that share a (coincident) vertex into one obstacle. Each obstacle's
|
|
256
|
+
// footprint is the convex hull of its vertices, dilated by the agent radius. Treating the obstacle
|
|
257
|
+
// as one polygon (rather than per-triangle) avoids self-touching/pinched union boundaries.
|
|
258
|
+
const parent = new Int32Array(overhead_count);
|
|
259
|
+
for (let i = 0; i < overhead_count; i++) parent[i] = i;
|
|
260
|
+
const find = (a) => { while (parent[a] !== a) { parent[a] = parent[parent[a]]; a = parent[a]; } return a; };
|
|
261
|
+
|
|
262
|
+
const key_to_tri = new Map();
|
|
263
|
+
for (let i = 0; i < overhead_count; i++) {
|
|
264
|
+
for (let k = 0; k < 3; k++) {
|
|
265
|
+
const ku = Math.round(overhead_uv[i * 6 + k * 2] * QUANTIZE);
|
|
266
|
+
const kv = Math.round(overhead_uv[i * 6 + k * 2 + 1] * QUANTIZE);
|
|
267
|
+
const key = (ku + KEY_BIAS) * KEY_STRIDE + (kv + KEY_BIAS);
|
|
268
|
+
const seen = key_to_tri.get(key);
|
|
269
|
+
if (seen !== undefined) parent[find(i)] = find(seen); else key_to_tri.set(key, i);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** @type {Map<number, {pts: number[], hsum: number, n: number}>} */
|
|
274
|
+
const groups = new Map();
|
|
275
|
+
for (let i = 0; i < overhead_count; i++) {
|
|
276
|
+
const root = find(i);
|
|
277
|
+
let g = groups.get(root);
|
|
278
|
+
if (g === undefined) { g = { pts: [], hsum: 0, n: 0 }; groups.set(root, g); }
|
|
279
|
+
for (let k = 0; k < 6; k++) g.pts.push(overhead_uv[i * 6 + k]);
|
|
280
|
+
g.hsum += overhead_h[i];
|
|
281
|
+
g.n++;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// each footprint: a CCW (dilated) convex polygon in (u,v) plus the obstacle's mean height
|
|
285
|
+
/** @type {Float64Array[]} */
|
|
286
|
+
const footprint_poly = [];
|
|
287
|
+
const footprint_h = [];
|
|
288
|
+
for (const g of groups.values()) {
|
|
289
|
+
const hull = convex_hull_monotone_2d(g.pts, g.pts.length / 2);
|
|
290
|
+
if (hull.length < 3) continue;
|
|
291
|
+
|
|
292
|
+
let poly = new Float64Array(hull.length * 2);
|
|
293
|
+
for (let i = 0; i < hull.length; i++) {
|
|
294
|
+
poly[i * 2] = g.pts[hull[i] * 2];
|
|
295
|
+
poly[i * 2 + 1] = g.pts[hull[i] * 2 + 1];
|
|
296
|
+
}
|
|
297
|
+
// ensure CCW (interior to the left of each edge) for the subtraction
|
|
298
|
+
if (compute_polygon_area_2d(poly, hull.length) < 0) {
|
|
299
|
+
const rev = new Float64Array(poly.length);
|
|
300
|
+
for (let i = 0; i < hull.length; i++) {
|
|
301
|
+
rev[i * 2] = poly[(hull.length - 1 - i) * 2];
|
|
302
|
+
rev[i * 2 + 1] = poly[(hull.length - 1 - i) * 2 + 1];
|
|
303
|
+
}
|
|
304
|
+
poly = rev;
|
|
305
|
+
}
|
|
306
|
+
if (agent_radius > 0) poly = offset_convex_ccw(poly, poly.length / 2, agent_radius);
|
|
307
|
+
|
|
308
|
+
footprint_poly.push(poly);
|
|
309
|
+
footprint_h.push(g.hsum / g.n);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const footprint_count = footprint_poly.length;
|
|
313
|
+
if (footprint_count === 0) return soup.slice(0, triangle_count * 9);
|
|
314
|
+
|
|
315
|
+
// Broad-phase index: one leaf per footprint, in (u, v, height) space. The first two axes are the
|
|
316
|
+
// dilated footprint's (u,v) extent; the third is the band of soup heights it can carve - a triangle
|
|
317
|
+
// at mean height `th` is carved by an obstacle at height `h` iff 0 < h - th <= agent_height. A soup
|
|
318
|
+
// triangle then queries this BVH with its own (u,v) extent at `th` to get just the candidate
|
|
319
|
+
// obstacles, instead of scanning all of them.
|
|
320
|
+
const footprint_bvh = new BVH();
|
|
321
|
+
footprint_bvh.release_all();
|
|
322
|
+
for (let fi = 0; fi < footprint_count; fi++) {
|
|
323
|
+
const poly = footprint_poly[fi];
|
|
324
|
+
let min_u = Infinity, min_v = Infinity, max_u = -Infinity, max_v = -Infinity;
|
|
325
|
+
for (let i = 0; i < poly.length; i += 2) {
|
|
326
|
+
const u = poly[i], v = poly[i + 1];
|
|
327
|
+
if (u < min_u) min_u = u;
|
|
328
|
+
if (u > max_u) max_u = u;
|
|
329
|
+
if (v < min_v) min_v = v;
|
|
330
|
+
if (v > max_v) max_v = v;
|
|
331
|
+
}
|
|
332
|
+
const h = footprint_h[fi];
|
|
333
|
+
const node = footprint_bvh.allocate_node();
|
|
334
|
+
footprint_bvh.node_set_user_data(node, fi);
|
|
335
|
+
footprint_bvh.node_set_aabb_primitive(
|
|
336
|
+
node,
|
|
337
|
+
min_u, min_v, h - agent_height - HEIGHT_EPS,
|
|
338
|
+
max_u, max_v, h - HEIGHT_EPS
|
|
339
|
+
);
|
|
340
|
+
footprint_bvh.insert_leaf(node);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const out = [];
|
|
344
|
+
|
|
345
|
+
// per-triangle reusable storage
|
|
346
|
+
const tri_uv = new Float64Array(6); // u0,v0,u1,v1,u2,v2 of the source triangle
|
|
347
|
+
const px = new Float64Array(3), py = new Float64Array(3), pz = new Float64Array(3);
|
|
348
|
+
const query_aabb = new Float64Array(6);
|
|
349
|
+
const query_hits = new Int32Array(footprint_count);
|
|
350
|
+
/** @type {Float64Array[]} */
|
|
351
|
+
let pieces = [];
|
|
352
|
+
/** @type {Float64Array[]} */
|
|
353
|
+
let next_pieces = [];
|
|
354
|
+
|
|
355
|
+
for (let t = 0; t < triangle_count; t++) {
|
|
356
|
+
const o = t * 9;
|
|
357
|
+
|
|
358
|
+
let th = 0;
|
|
359
|
+
for (let k = 0; k < 3; k++) {
|
|
360
|
+
const x = soup[o + k * 3], y = soup[o + k * 3 + 1], z = soup[o + k * 3 + 2];
|
|
361
|
+
px[k] = x; py[k] = y; pz[k] = z;
|
|
362
|
+
tri_uv[k * 2] = v3_dot(x, y, z, u_x, u_y, u_z);
|
|
363
|
+
tri_uv[k * 2 + 1] = v3_dot(x, y, z, v_x, v_y, v_z);
|
|
364
|
+
th += v3_dot(x, y, z, upx, upy, upz);
|
|
365
|
+
}
|
|
366
|
+
th /= 3;
|
|
367
|
+
|
|
368
|
+
// barycentric denominator (twice the signed uv area of the triangle)
|
|
369
|
+
const e1u = tri_uv[2] - tri_uv[0], e1v = tri_uv[3] - tri_uv[1];
|
|
370
|
+
const e2u = tri_uv[4] - tri_uv[0], e2v = tri_uv[5] - tri_uv[1];
|
|
371
|
+
const det = e1u * e2v - e2u * e1v;
|
|
372
|
+
|
|
373
|
+
if (Math.abs(det) <= AREA_EPS) {
|
|
374
|
+
// degenerate in (u,v) (a near-vertical triangle): cannot carve meaningfully, pass through
|
|
375
|
+
for (let i = 0; i < 9; i++) out.push(soup[o + i]);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// broad-phase: only the obstacles whose (u,v) extent and height band overlap this triangle
|
|
380
|
+
let min_u = tri_uv[0], max_u = tri_uv[0], min_v = tri_uv[1], max_v = tri_uv[1];
|
|
381
|
+
for (let k = 1; k < 3; k++) {
|
|
382
|
+
const u = tri_uv[k * 2], v = tri_uv[k * 2 + 1];
|
|
383
|
+
if (u < min_u) min_u = u;
|
|
384
|
+
if (u > max_u) max_u = u;
|
|
385
|
+
if (v < min_v) min_v = v;
|
|
386
|
+
if (v > max_v) max_v = v;
|
|
387
|
+
}
|
|
388
|
+
query_aabb[0] = min_u; query_aabb[1] = min_v; query_aabb[2] = th;
|
|
389
|
+
query_aabb[3] = max_u; query_aabb[4] = max_v; query_aabb[5] = th;
|
|
390
|
+
|
|
391
|
+
const hit_count = bvh_query_user_data_overlaps_aabb(query_hits, 0, footprint_bvh, query_aabb);
|
|
392
|
+
if (hit_count === 0) {
|
|
393
|
+
// no obstacle overlaps here - copy straight through
|
|
394
|
+
for (let i = 0; i < 9; i++) out.push(soup[o + i]);
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// start with the whole triangle as one piece, then subtract each overlapping footprint
|
|
399
|
+
pieces.length = 0;
|
|
400
|
+
pieces.push(Float64Array.of(tri_uv[0], tri_uv[1], tri_uv[2], tri_uv[3], tri_uv[4], tri_uv[5]));
|
|
401
|
+
|
|
402
|
+
for (let qi = 0; qi < hit_count; qi++) {
|
|
403
|
+
const fi = query_hits[qi];
|
|
404
|
+
const dh = footprint_h[fi] - th;
|
|
405
|
+
if (dh <= HEIGHT_EPS || dh > agent_height + HEIGHT_EPS) continue; // exact band test
|
|
406
|
+
|
|
407
|
+
const fp = footprint_poly[fi];
|
|
408
|
+
const fp_n = fp.length / 2;
|
|
409
|
+
|
|
410
|
+
next_pieces.length = 0;
|
|
411
|
+
for (let pi = 0; pi < pieces.length; pi++) {
|
|
412
|
+
subtract_footprint(pieces[pi], pieces[pi].length / 2, fp, fp_n, next_pieces);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const swap = pieces; pieces = next_pieces; next_pieces = swap;
|
|
416
|
+
if (pieces.length === 0) break;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const inv_det = 1 / det;
|
|
420
|
+
for (let pi = 0; pi < pieces.length; pi++) {
|
|
421
|
+
const poly = pieces[pi];
|
|
422
|
+
const poly_n = poly.length / 2;
|
|
423
|
+
|
|
424
|
+
// fan-triangulate the surviving piece and reconstruct world XYZ per vertex
|
|
425
|
+
for (let i = 1; i + 1 < poly_n; i++) {
|
|
426
|
+
const u0 = poly[0], v0 = poly[1];
|
|
427
|
+
const u1 = poly[i * 2], v1 = poly[i * 2 + 1];
|
|
428
|
+
const u2 = poly[(i + 1) * 2], v2 = poly[(i + 1) * 2 + 1];
|
|
429
|
+
|
|
430
|
+
const area2 = Math.abs((u1 - u0) * (v2 - v0) - (u2 - u0) * (v1 - v0));
|
|
431
|
+
if (area2 < 2 * AREA_EPS) continue;
|
|
432
|
+
|
|
433
|
+
emit_xyz(out, u0, v0, tri_uv, px, py, pz, e1u, e1v, e2u, e2v, inv_det);
|
|
434
|
+
emit_xyz(out, u1, v1, tri_uv, px, py, pz, e1u, e1v, e2u, e2v, inv_det);
|
|
435
|
+
emit_xyz(out, u2, v2, tri_uv, px, py, pz, e1u, e1v, e2u, e2v, inv_det);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Reconstruct the world XYZ of a (u,v) point lying in a source triangle (barycentric on the triangle's
|
|
445
|
+
* planar uv coordinates) and append it to `out`.
|
|
446
|
+
*
|
|
447
|
+
* @param {number[]} out destination soup
|
|
448
|
+
* @param {number} u
|
|
449
|
+
* @param {number} v the point in (u,v)
|
|
450
|
+
* @param {Float64Array} tri_uv the triangle's uv corners (stride 2, vertex 0 is the origin)
|
|
451
|
+
* @param {Float64Array} px
|
|
452
|
+
* @param {Float64Array} py
|
|
453
|
+
* @param {Float64Array} pz the triangle's world corners
|
|
454
|
+
* @param {number} e1u
|
|
455
|
+
* @param {number} e1v edge (v1 - v0) in uv
|
|
456
|
+
* @param {number} e2u
|
|
457
|
+
* @param {number} e2v edge (v2 - v0) in uv
|
|
458
|
+
* @param {number} inv_det reciprocal of the uv area determinant
|
|
459
|
+
*/
|
|
460
|
+
function emit_xyz(out, u, v, tri_uv, px, py, pz, e1u, e1v, e2u, e2v, inv_det) {
|
|
461
|
+
const du = u - tri_uv[0];
|
|
462
|
+
const dv = v - tri_uv[1];
|
|
463
|
+
const w1 = (du * e2v - e2u * dv) * inv_det;
|
|
464
|
+
const w2 = (e1u * dv - du * e1v) * inv_det;
|
|
465
|
+
const w0 = 1 - w1 - w2;
|
|
466
|
+
|
|
467
|
+
out.push(
|
|
468
|
+
w0 * px[0] + w1 * px[1] + w2 * px[2],
|
|
469
|
+
w0 * py[0] + w1 * py[1] + w2 * py[2],
|
|
470
|
+
w0 * pz[0] + w1 * pz[1] + w2 * pz[2]
|
|
471
|
+
);
|
|
472
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navmesh_build_topology.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/navigation/mesh/build/navmesh_build_topology.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"navmesh_build_topology.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/navigation/mesh/build/navmesh_build_topology.js"],"names":[],"mappings":"AA8CA;;;;;;;;;;GAUG;AACH,wKATW,cAAc,QAmMxB;+BAlP8B,mEAAmE"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { assert } from "../../../../core/assert.js";
|
|
2
|
-
import { BVH } from "../../../../core/bvh2/bvh3/BVH.js";
|
|
3
2
|
import { BinaryTopology } from "../../../../core/geom/3d/topology/struct/binary/BinaryTopology.js";
|
|
4
3
|
import {
|
|
5
4
|
bt_mesh_cleanup_faceless_references
|
|
@@ -36,15 +35,13 @@ import {
|
|
|
36
35
|
} from "../../../../core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js";
|
|
37
36
|
import { v3_angle_between } from "../../../../core/geom/vec3/v3_angle_between.js";
|
|
38
37
|
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
38
|
import {
|
|
43
39
|
bt_mesh_resolve_t_junctions
|
|
44
40
|
} from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_resolve_t_junctions.js";
|
|
45
41
|
import {
|
|
46
42
|
bt_mesh_fill_small_holes
|
|
47
43
|
} from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js";
|
|
44
|
+
import { clip_soup_against_overhangs } from "./clip_soup_against_overhangs.js";
|
|
48
45
|
|
|
49
46
|
|
|
50
47
|
/**
|
|
@@ -85,11 +82,6 @@ export function navmesh_build_topology({
|
|
|
85
82
|
// outward winding; that remains an input contract we cannot recover from here.
|
|
86
83
|
bt_mesh_compute_face_normals(source);
|
|
87
84
|
|
|
88
|
-
const source_bvh = new BVH();
|
|
89
|
-
|
|
90
|
-
// prepare a BVH, we'll need it for height queries later on
|
|
91
|
-
bvh_build_from_bt_mesh(source_bvh, source);
|
|
92
|
-
|
|
93
85
|
const scratch_normal = new Float32Array(3);
|
|
94
86
|
|
|
95
87
|
// unpack topology into triangle soup
|
|
@@ -103,7 +95,7 @@ export function navmesh_build_topology({
|
|
|
103
95
|
*
|
|
104
96
|
* @type {number[]}
|
|
105
97
|
*/
|
|
106
|
-
|
|
98
|
+
let raw_triangles = [];
|
|
107
99
|
let triangle_count = 0;
|
|
108
100
|
|
|
109
101
|
for (let face_id = 0; face_id < source_face_count; face_id++) {
|
|
@@ -147,14 +139,6 @@ export function navmesh_build_topology({
|
|
|
147
139
|
}
|
|
148
140
|
|
|
149
141
|
{
|
|
150
|
-
const bvh = new BVH();
|
|
151
|
-
|
|
152
|
-
// first dump all triangles into a BVH for speed of access
|
|
153
|
-
bvh_build_from_unindexed_triangles(bvh, raw_triangles, triangle_count);
|
|
154
|
-
|
|
155
|
-
// find possible triangle edge connections, that is - where edges of 2 triangles touch, but don't match exactly.
|
|
156
|
-
// the touching edges will need to be cut and affected triangles split accordingly
|
|
157
|
-
|
|
158
142
|
const mesh = new BinaryTopology();
|
|
159
143
|
|
|
160
144
|
// raw_triangles may have stale tail data from filtering steps above (e.g. degenerate/steep);
|
|
@@ -180,27 +164,10 @@ export function navmesh_build_topology({
|
|
|
180
164
|
bt_mesh_fuse_duplicate_edges(mesh);
|
|
181
165
|
}
|
|
182
166
|
|
|
183
|
-
// ---
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
// a smooth, consistent outline around obstacles from a single, robust pass.
|
|
188
|
-
if (agent_height > 0) {
|
|
189
|
-
bt_mesh_carve_height_clearance({
|
|
190
|
-
mesh,
|
|
191
|
-
source,
|
|
192
|
-
source_bvh,
|
|
193
|
-
agent_height,
|
|
194
|
-
agent_radius: 0,
|
|
195
|
-
up,
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
bt_mesh_compact(mesh);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// --- Then erode every boundary (outer edge + obstacle holes) by the agent radius ---
|
|
202
|
-
// The erosion uses an exact Euclidean distance field with cut-band refinement, so it stays
|
|
203
|
-
// accurate and connected even on the hole-punched mesh the carve produces.
|
|
167
|
+
// --- Erode the outer boundary by the agent radius (no obstacles carved yet) ---
|
|
168
|
+
// The erosion uses an exact Euclidean distance field with cut-band refinement, so the inset is
|
|
169
|
+
// uniform. Obstacles are carved afterwards (below) as pre-dilated footprints, so they are not
|
|
170
|
+
// eroded here a second time.
|
|
204
171
|
{
|
|
205
172
|
const islands = bt_mesh_compute_face_islands(mesh);
|
|
206
173
|
|
|
@@ -214,6 +181,36 @@ export function navmesh_build_topology({
|
|
|
214
181
|
bt_mesh_compact(mesh);
|
|
215
182
|
}
|
|
216
183
|
|
|
184
|
+
// --- Carve obstacle footprints, pre-dilated by the agent radius with rounded corners ---
|
|
185
|
+
// Subtract each overhead obstacle's r-dilated footprint from the (already outer-eroded) surface.
|
|
186
|
+
// The hole boundary is then the true offset of the obstacle - straight along edges, a clean arc
|
|
187
|
+
// at corners - instead of the wavy / mitred contour that sampling-then-eroding produced.
|
|
188
|
+
if (agent_height > 0) {
|
|
189
|
+
const soup = [];
|
|
190
|
+
const sv = [0, 0, 0];
|
|
191
|
+
for (let f = 0; f < mesh.faces.size; f++) {
|
|
192
|
+
if (!mesh.faces.is_allocated(f)) continue;
|
|
193
|
+
let l = mesh.face_read_loop(f);
|
|
194
|
+
for (let i = 0; i < 3; i++) {
|
|
195
|
+
mesh.vertex_read_coordinate(sv, 0, mesh.loop_read_vertex(l));
|
|
196
|
+
soup.push(sv[0], sv[1], sv[2]);
|
|
197
|
+
l = mesh.loop_read_next(l);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const carved = clip_soup_against_overhangs(soup, (soup.length / 9) | 0, source, agent_height, agent_radius, up);
|
|
202
|
+
|
|
203
|
+
const carved_mesh = new BinaryTopology();
|
|
204
|
+
bt_mesh_from_unindexed_geometry(carved_mesh, carved);
|
|
205
|
+
bt_merge_verts_by_distance(carved_mesh, 1e-6);
|
|
206
|
+
bt_mesh_fuse_duplicate_edges(carved_mesh);
|
|
207
|
+
if (bt_mesh_resolve_t_junctions(carved_mesh, 1e-4) > 0) {
|
|
208
|
+
bt_merge_verts_by_distance(carved_mesh, 1e-6);
|
|
209
|
+
bt_mesh_fuse_duplicate_edges(carved_mesh);
|
|
210
|
+
}
|
|
211
|
+
mesh.copy(carved_mesh);
|
|
212
|
+
}
|
|
213
|
+
|
|
217
214
|
// bridge across small steps / gaps so stair-separated or slightly-broken tiers are reachable.
|
|
218
215
|
// Opt-in: with both step params 0 (the default) the topology is left untouched.
|
|
219
216
|
if (agent_max_step_height > 0 || agent_max_step_distance > 0) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the exact shortest path from (sx,sy,sz) in `start_face` to (gx,gy,gz) in `goal_face`.
|
|
3
|
+
*
|
|
4
|
+
* @param {number[]|Float64Array|Float32Array} output path written as flat [x0,y0,z0, x1,y1,z1, ...]
|
|
5
|
+
* @param {BinaryTopology} topology triangle mesh (3D vertex coordinates)
|
|
6
|
+
* @param {number} sx
|
|
7
|
+
* @param {number} sy
|
|
8
|
+
* @param {number} sz start point (assumed on/near `start_face`)
|
|
9
|
+
* @param {number} start_face triangle containing the start point
|
|
10
|
+
* @param {number} gx
|
|
11
|
+
* @param {number} gy
|
|
12
|
+
* @param {number} gz goal point (assumed on/near `goal_face`)
|
|
13
|
+
* @param {number} goal_face triangle containing the goal point
|
|
14
|
+
* @returns {number} number of path POINTS written (3 numbers each), 0 if no path
|
|
15
|
+
*/
|
|
16
|
+
export function navmesh_polyanya_find_path(output: number[] | Float64Array | Float32Array, topology: BinaryTopology, sx: number, sy: number, sz: number, start_face: number, gx: number, gy: number, gz: number, goal_face: number): number;
|
|
17
|
+
//# sourceMappingURL=navmesh_polyanya_find_path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navmesh_polyanya_find_path.d.ts","sourceRoot":"","sources":["../../../../../src/engine/navigation/mesh/navmesh_polyanya_find_path.js"],"names":[],"mappings":"AAqTA;;;;;;;;;;;;;;GAcG;AACH,mDAZW,MAAM,EAAE,GAAC,YAAY,GAAC,YAAY,gCAElC,MAAM,MACN,MAAM,MACN,MAAM,cACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,aACN,MAAM,GACJ,MAAM,CAmFlB"}
|