@woosh/meep-engine 2.148.0 → 2.150.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.d.ts +23 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_bridge_islands.js +295 -0
- package/src/engine/graphics/GraphicsEngine.d.ts.map +1 -1
- package/src/engine/graphics/GraphicsEngine.js +18 -8
- package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map +1 -1
- package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js +8 -5
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.js +18 -10
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOUpscaleShader.js +1 -1
- package/src/engine/navigation/mesh/NavigationMesh.d.ts +6 -2
- package/src/engine/navigation/mesh/NavigationMesh.d.ts.map +1 -1
- package/src/engine/navigation/mesh/NavigationMesh.js +234 -212
- package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts +7 -3
- package/src/engine/navigation/mesh/bt_mesh_face_find_path.d.ts.map +1 -1
- package/src/engine/navigation/mesh/bt_mesh_face_find_path.js +67 -73
- package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts +16 -5
- package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.d.ts.map +1 -1
- package/src/engine/navigation/mesh/build/enforce_agent_height_clearance.js +262 -147
- package/src/engine/navigation/mesh/build/navmesh_build_topology.d.ts.map +1 -1
- package/src/engine/navigation/mesh/build/navmesh_build_topology.js +33 -3
- package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts +4 -1
- package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts.map +1 -1
- package/src/engine/navigation/mesh/bvh_query_nearest_face.js +164 -131
- package/src/engine/physics/broadphase/generate_pairs.js +110 -110
- package/src/engine/physics/queries/raycast.js +201 -201
|
@@ -1,71 +1,174 @@
|
|
|
1
1
|
import { assert } from "../../../../core/assert.js";
|
|
2
|
-
import {
|
|
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";
|
|
3
7
|
import { compute_triangle_area_3d } from "../../../../core/geom/3d/triangle/compute_triangle_area_3d.js";
|
|
4
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
computeTriangleRayIntersectionBarycentric
|
|
10
|
+
} from "../../../../core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js";
|
|
5
11
|
import { clamp } from "../../../../core/math/clamp.js";
|
|
6
|
-
import { roundFair } from "../../../../core/math/random/roundFair.js";
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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);
|
|
17
33
|
|
|
18
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.
|
|
19
38
|
*
|
|
20
|
-
* @param {
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {number
|
|
23
|
-
* @param {number}
|
|
24
|
-
* @param {number}
|
|
25
|
-
* @param {number}
|
|
26
|
-
* @param {number}
|
|
27
|
-
* @param {number}
|
|
28
|
-
* @param {number}
|
|
29
|
-
* @
|
|
30
|
-
* @param {number} cy
|
|
31
|
-
* @param {number} cz
|
|
32
|
-
* @returns {number} new triangles added to the output
|
|
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}
|
|
33
49
|
*/
|
|
34
|
-
function
|
|
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(
|
|
35
108
|
output,
|
|
36
|
-
output_offset,
|
|
37
|
-
hole_shapes,
|
|
38
109
|
ax, ay, az,
|
|
39
110
|
bx, by, bz,
|
|
40
|
-
cx, cy, cz
|
|
111
|
+
cx, cy, cz
|
|
41
112
|
) {
|
|
42
|
-
|
|
113
|
+
output.push(ax, ay, az, bx, by, bz, cx, cy, cz);
|
|
43
114
|
}
|
|
44
115
|
|
|
45
116
|
/**
|
|
117
|
+
* Knock holes in the walkable triangle soup wherever an agent of the given height would not fit under
|
|
118
|
+
* overhead geometry.
|
|
46
119
|
*
|
|
47
|
-
*
|
|
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
|
|
48
130
|
* @param {number} agent_height
|
|
49
131
|
* @param {number} agent_radius
|
|
50
|
-
* @param {number} triangle_count
|
|
51
|
-
* @param {number[]} triangles
|
|
52
|
-
* @param {
|
|
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
|
|
53
135
|
* @returns {number} new triangle count
|
|
54
136
|
*/
|
|
55
137
|
export function enforce_agent_height_clearance({
|
|
56
138
|
bvh,
|
|
139
|
+
source,
|
|
57
140
|
agent_height,
|
|
58
141
|
agent_radius,
|
|
59
142
|
triangle_count,
|
|
60
143
|
triangles,
|
|
61
|
-
|
|
144
|
+
up,
|
|
62
145
|
}) {
|
|
63
|
-
console.warn('enforce_agent_height_clearance() is not implemented');
|
|
64
146
|
|
|
65
|
-
|
|
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;
|
|
66
168
|
|
|
67
169
|
/**
|
|
68
|
-
*
|
|
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.
|
|
69
172
|
* @type {number}
|
|
70
173
|
*/
|
|
71
174
|
const sampling_resolution = Math.min(
|
|
@@ -73,132 +176,144 @@ export function enforce_agent_height_clearance({
|
|
|
73
176
|
Math.max(agent_radius / 2, 0.001)
|
|
74
177
|
);
|
|
75
178
|
|
|
76
|
-
assert.notNaN(sampling_resolution, 'sampling_resolution');
|
|
77
179
|
assert.isFinite(sampling_resolution, 'sampling_resolution');
|
|
180
|
+
assert.greaterThan(sampling_resolution, 0, 'sampling_resolution');
|
|
78
181
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let current_triangle_count = triangle_count;
|
|
86
|
-
|
|
87
|
-
for (let i = 0; i < current_triangle_count; i++) {
|
|
88
|
-
const triangle_address = i * 9;
|
|
182
|
+
/**
|
|
183
|
+
* Surviving triangles. Built separately because the count can grow (subdivision), then copied back.
|
|
184
|
+
* @type {number[]}
|
|
185
|
+
*/
|
|
186
|
+
const output = [];
|
|
89
187
|
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
const az = triangles[triangle_address + 2];
|
|
188
|
+
for (let i = 0; i < triangle_count; i++) {
|
|
189
|
+
const address = i * 9;
|
|
93
190
|
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
const
|
|
191
|
+
const ax = triangles[address];
|
|
192
|
+
const ay = triangles[address + 1];
|
|
193
|
+
const az = triangles[address + 2];
|
|
97
194
|
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
195
|
+
const bx = triangles[address + 3];
|
|
196
|
+
const by = triangles[address + 4];
|
|
197
|
+
const bz = triangles[address + 5];
|
|
101
198
|
|
|
102
|
-
|
|
103
|
-
|
|
199
|
+
const cx = triangles[address + 6];
|
|
200
|
+
const cy = triangles[address + 7];
|
|
201
|
+
const cz = triangles[address + 8];
|
|
104
202
|
|
|
105
|
-
const
|
|
106
|
-
ax, ay, az,
|
|
107
|
-
bx, by, bz,
|
|
108
|
-
cx, cy, cz
|
|
109
|
-
);
|
|
203
|
+
const area = compute_triangle_area_3d(ax, ay, az, bx, by, bz, cx, cy, cz);
|
|
110
204
|
|
|
111
|
-
if (
|
|
112
|
-
//
|
|
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);
|
|
113
208
|
continue;
|
|
114
209
|
}
|
|
115
210
|
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const x = ax + r0 * e0x + r1 * e1x;
|
|
161
|
-
const y = ay + r0 * e0y + r1 * e1y;
|
|
162
|
-
const z = az + r0 * e0z + r1 * e1z;
|
|
163
|
-
|
|
164
|
-
ray.setOrigin(x, y, z);
|
|
165
|
-
|
|
166
|
-
// tiny offset to avoid self-occlusion
|
|
167
|
-
ray.shiftForward(1e-7);
|
|
168
|
-
|
|
169
|
-
// TODO actual raycast
|
|
170
|
-
|
|
171
|
-
// if we got a hit - create a hole triangle, if no hit - continue
|
|
172
|
-
holes.push(r0, r1);
|
|
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
|
+
}
|
|
173
254
|
}
|
|
174
255
|
|
|
175
|
-
if (
|
|
176
|
-
//
|
|
177
|
-
|
|
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
|
+
}
|
|
178
264
|
}
|
|
265
|
+
// else fully blocked - drop the triangle entirely
|
|
266
|
+
}
|
|
179
267
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
cx, cy, cz
|
|
186
|
-
);
|
|
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;
|
|
187
273
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
triangle_count,
|
|
191
|
-
hole_shapes,
|
|
192
|
-
ax, ay, az,
|
|
193
|
-
bx, by, bz,
|
|
194
|
-
cx, cy, cz
|
|
195
|
-
);
|
|
274
|
+
return (output.length / 9) | 0;
|
|
275
|
+
}
|
|
196
276
|
|
|
197
|
-
|
|
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
|
+
);
|
|
198
313
|
|
|
199
|
-
|
|
314
|
+
if (clear) {
|
|
315
|
+
out.push(p0x, p0y, p0z, p1x, p1y, p1z, p2x, p2y, p2z);
|
|
200
316
|
}
|
|
201
317
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
318
|
+
return clear;
|
|
319
|
+
}
|
|
@@ -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":"AA2CA;;;;;;;;;;GAUG;AACH,wKATW,cAAc,QAoLxB;+BA/N8B,mEAAmE"}
|
|
@@ -4,6 +4,9 @@ import { BinaryTopology } from "../../../../core/geom/3d/topology/struct/binary/
|
|
|
4
4
|
import {
|
|
5
5
|
bt_mesh_cleanup_faceless_references
|
|
6
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";
|
|
7
10
|
import {
|
|
8
11
|
bt_mesh_compute_face_normals
|
|
9
12
|
} from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js";
|
|
@@ -25,12 +28,14 @@ import {
|
|
|
25
28
|
bt_merge_verts_by_distance
|
|
26
29
|
} from "../../../../core/geom/3d/topology/struct/binary/io/vertex/bt_merge_verts_by_distance.js";
|
|
27
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";
|
|
28
34
|
import {
|
|
29
35
|
bt_mesh_compute_face_islands
|
|
30
36
|
} from "../../../../core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js";
|
|
31
37
|
import { v3_angle_between } from "../../../../core/geom/vec3/v3_angle_between.js";
|
|
32
38
|
import Vector3 from "../../../../core/geom/Vector3.js";
|
|
33
|
-
import { seededRandom } from "../../../../core/math/random/seededRandom.js";
|
|
34
39
|
import { bvh_build_from_bt_mesh } from "../bvh_build_from_bt_mesh.js";
|
|
35
40
|
import { bvh_build_from_unindexed_triangles } from "./bvh_build_from_unindexed_triangles.js";
|
|
36
41
|
import { enforce_agent_height_clearance } from "./enforce_agent_height_clearance.js";
|
|
@@ -67,7 +72,12 @@ export function navmesh_build_topology({
|
|
|
67
72
|
assert.greaterThanOrEqual(agent_max_step_height, 0, 'agent_max_step_height');
|
|
68
73
|
assert.greaterThanOrEqual(agent_max_step_distance, 0, 'agent_max_step_distance');
|
|
69
74
|
|
|
70
|
-
|
|
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);
|
|
71
81
|
|
|
72
82
|
const source_bvh = new BVH();
|
|
73
83
|
|
|
@@ -135,11 +145,12 @@ export function navmesh_build_topology({
|
|
|
135
145
|
|
|
136
146
|
triangle_count = enforce_agent_height_clearance({
|
|
137
147
|
bvh: source_bvh,
|
|
148
|
+
source: source,
|
|
138
149
|
agent_height: agent_height,
|
|
139
150
|
agent_radius: agent_radius,
|
|
140
151
|
triangle_count: triangle_count,
|
|
141
152
|
triangles: raw_triangles,
|
|
142
|
-
|
|
153
|
+
up: up,
|
|
143
154
|
});
|
|
144
155
|
|
|
145
156
|
}
|
|
@@ -186,6 +197,25 @@ export function navmesh_build_topology({
|
|
|
186
197
|
// remove dangling references
|
|
187
198
|
bt_mesh_cleanup_faceless_references(mesh);
|
|
188
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
|
+
|
|
189
219
|
// face normals are consumed by navigation queries (string-pulling portal normals), populate them now
|
|
190
220
|
bt_mesh_compute_face_normals(mesh);
|
|
191
221
|
|
|
@@ -8,8 +8,11 @@
|
|
|
8
8
|
* @param {number} x
|
|
9
9
|
* @param {number} y
|
|
10
10
|
* @param {number} z
|
|
11
|
+
* @param {Float32Array|number[]} out_point the closest point ON the winning face's surface (the snapped
|
|
12
|
+
* contact point) is written here when a face is found. Required, must not be null.
|
|
13
|
+
* @param {number} [out_point_offset=0] offset into `out_point` to write the snapped XYZ triple
|
|
11
14
|
* @param {number} [max_distance=Infinity] optional cutoff, only faces within this distance are considered
|
|
12
15
|
* @returns {number} face ID of the nearest face, or {@link NULL_POINTER} if no face was found within the cutoff
|
|
13
16
|
*/
|
|
14
|
-
export function bvh_query_nearest_face(bvh: BVH, mesh: BinaryTopology, x: number, y: number, z: number, max_distance?: number): number;
|
|
17
|
+
export function bvh_query_nearest_face(bvh: BVH, mesh: BinaryTopology, x: number, y: number, z: number, out_point: Float32Array | number[], out_point_offset?: number, max_distance?: number): number;
|
|
15
18
|
//# sourceMappingURL=bvh_query_nearest_face.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bvh_query_nearest_face.d.ts","sourceRoot":"","sources":["../../../../../src/engine/navigation/mesh/bvh_query_nearest_face.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bvh_query_nearest_face.d.ts","sourceRoot":"","sources":["../../../../../src/engine/navigation/mesh/bvh_query_nearest_face.js"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;;GAeG;AACH,0EATW,MAAM,KACN,MAAM,KACN,MAAM,aACN,YAAY,GAAC,MAAM,EAAE,qBAErB,MAAM,iBACN,MAAM,GACJ,MAAM,CA+HlB"}
|