@woosh/meep-engine 2.163.9 → 2.163.11
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/bvh2/bvh3/query/bvh_query_user_data_overlaps_capsule.d.ts +22 -0
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_capsule.d.ts.map +1 -0
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_capsule.js +101 -0
- package/src/core/collection/heap/IndexedFloatMaxHeap.d.ts.map +1 -0
- package/src/core/{graph/metis/native/refine → collection/heap}/IndexedFloatMaxHeap.js +1 -1
- package/src/core/geom/3d/capsule/capsule_intersects_aabb3_closed.d.ts +40 -0
- package/src/core/geom/3d/capsule/capsule_intersects_aabb3_closed.d.ts.map +1 -0
- package/src/core/geom/3d/capsule/capsule_intersects_aabb3_closed.js +67 -0
- package/src/core/geom/3d/capsule/capsule_intersects_aabb3_iterative.d.ts +45 -0
- package/src/core/geom/3d/capsule/capsule_intersects_aabb3_iterative.d.ts.map +1 -0
- package/src/core/geom/3d/capsule/capsule_intersects_aabb3_iterative.js +137 -0
- package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +146 -0
- package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_aabb3_t.d.ts +44 -0
- package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_aabb3_t.d.ts.map +1 -0
- package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_aabb3_t.js +153 -0
- package/src/core/geom/3d/topology/struct/binary/BinaryTopology.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/BinaryTopology.js +18 -7
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_resolve_t_junctions.d.ts +6 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_resolve_t_junctions.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_resolve_t_junctions.js +139 -95
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +2 -26
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify_by_error.d.ts +19 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify_by_error.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify_by_error.js +555 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_swap_vertex_slots.d.ts +13 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_swap_vertex_slots.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_swap_vertex_slots.js +28 -0
- package/src/core/graph/metis/native/bisection/BisectionScratch.d.ts +1 -1
- package/src/core/graph/metis/native/bisection/BisectionScratch.d.ts.map +1 -1
- package/src/core/graph/metis/native/bisection/BisectionScratch.js +1 -1
- package/src/core/graph/metis/native/refine/RefinementScratch.d.ts +1 -1
- package/src/core/graph/metis/native/refine/RefinementScratch.d.ts.map +1 -1
- package/src/core/graph/metis/native/refine/RefinementScratch.js +1 -1
- package/src/engine/navigation/mesh/build/navmesh_build_topology.d.ts.map +1 -1
- package/src/engine/navigation/mesh/build/navmesh_build_topology.js +25 -0
- package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/capsule_contacts.js +14 -21
- package/src/core/graph/metis/native/refine/IndexedFloatMaxHeap.d.ts.map +0 -1
- /package/src/core/{graph/metis/native/refine → collection/heap}/IndexedFloatMaxHeap.d.ts +0 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { clamp } from "../../../math/clamp.js";
|
|
2
|
+
import { v3_dot } from "../../vec3/v3_dot.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The argmin of `g(t)=distanceSqr(P(t),box)` is an INTERVAL when the minimum is
|
|
6
|
+
* non-unique (segment parallel to a face, or lying inside the box — the closest
|
|
7
|
+
* distance is then constant along a sub-segment). We track that interval as the
|
|
8
|
+
* monotone sign-structure of `g'(t)` is scanned and return its MIDPOINT, so the
|
|
9
|
+
* reported closest point is the central one rather than an arbitrary endpoint.
|
|
10
|
+
* Module-level (single-threaded, non-reentrant — same convention as the
|
|
11
|
+
* narrowphase `g_ct_*` accumulators):
|
|
12
|
+
* a = (max t, g) with g' ≤ 0, b = (min t, g) with g' > 0 → t_hi (rightmost min)
|
|
13
|
+
* c = (max t, g) with g' < 0, d = (min t, g) with g' ≥ 0 → t_lo (leftmost min)
|
|
14
|
+
*/
|
|
15
|
+
let _a_t = -1, _a_g = 0;
|
|
16
|
+
let _b_t = 2, _b_g = 0;
|
|
17
|
+
let _c_t = -1, _c_g = 0;
|
|
18
|
+
let _d_t = 2, _d_g = 0;
|
|
19
|
+
|
|
20
|
+
/** Fold one candidate `(t, g'(t))` into the argmin-interval trackers. */
|
|
21
|
+
function _consider(t, g) {
|
|
22
|
+
if (g <= 0 && t > _a_t) { _a_t = t; _a_g = g; }
|
|
23
|
+
if (g > 0 && t < _b_t) { _b_t = t; _b_g = g; }
|
|
24
|
+
if (g < 0 && t > _c_t) { _c_t = t; _c_g = g; }
|
|
25
|
+
if (g >= 0 && t < _d_t) { _d_t = t; _d_g = g; }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parameter `t ∈ [0,1]` of the point on the segment `p0 → p1` nearest to an
|
|
30
|
+
* axis-aligned box. The closest segment point is `p0 + t·(p1 − p0)`; the closest
|
|
31
|
+
* box point is that point clamped per-axis into the box. Sibling of
|
|
32
|
+
* {@link line3_compute_segment_nearest_point_to_point_t} (segment-vs-point).
|
|
33
|
+
*
|
|
34
|
+
* Exact and O(1). `g(t) = distanceSqr(P(t), box)` is convex and — for an AABB —
|
|
35
|
+
* piecewise-quadratic, with pieces meeting only where `P(t)` crosses one of the
|
|
36
|
+
* six box face planes (≤6 crossings). `g'(t) ∝ (P(t) − clamp(P(t), box)) · d` is
|
|
37
|
+
* therefore piecewise-linear and monotonically non-decreasing, so the minimiser
|
|
38
|
+
* is found in closed form by evaluating `g'` at the ≤8 candidates
|
|
39
|
+
* `{0, 1, face-plane crossings}`: the minimum is achieved on the interval where
|
|
40
|
+
* `g' = 0` (a single point when strictly convex), and this returns that
|
|
41
|
+
* interval's midpoint. No iteration, no tolerance, no Cramer's-Rule solve.
|
|
42
|
+
*
|
|
43
|
+
* When the minimum is non-unique (segment parallel to / inside the box) the
|
|
44
|
+
* midpoint is the *central* closest point — what the narrowphase
|
|
45
|
+
* `capsule_box_contact` needs for a stable resting manifold; a degenerate
|
|
46
|
+
* (`p0 === p1`) segment returns `0` (its parameter is arbitrary).
|
|
47
|
+
*
|
|
48
|
+
* The AABB specialisation of the Eberly/Ericson closest-point-segment-vs-box
|
|
49
|
+
* reduction. Backs {@link capsule_intersects_aabb3_closed} and the narrowphase
|
|
50
|
+
* `capsule_box_contact`; it replaced a 2-iteration alternating projection that
|
|
51
|
+
* under-detected and mis-placed contacts for segments near-parallel to a box
|
|
52
|
+
* face/edge (see `core/geom/3d/gjk/GJK_REVIEW_NOTES.md`).
|
|
53
|
+
*
|
|
54
|
+
* Box bounds are assumed ordered per axis (`box_*0 ≤ box_*1`).
|
|
55
|
+
*
|
|
56
|
+
* @param {number} p0_x segment start x
|
|
57
|
+
* @param {number} p0_y
|
|
58
|
+
* @param {number} p0_z
|
|
59
|
+
* @param {number} p1_x segment end x
|
|
60
|
+
* @param {number} p1_y
|
|
61
|
+
* @param {number} p1_z
|
|
62
|
+
* @param {number} box_x0 box min x
|
|
63
|
+
* @param {number} box_y0
|
|
64
|
+
* @param {number} box_z0
|
|
65
|
+
* @param {number} box_x1 box max x
|
|
66
|
+
* @param {number} box_y1
|
|
67
|
+
* @param {number} box_z1
|
|
68
|
+
* @returns {number} t in [0, 1]
|
|
69
|
+
*/
|
|
70
|
+
export function line3_compute_segment_nearest_point_to_aabb3_t(
|
|
71
|
+
p0_x, p0_y, p0_z,
|
|
72
|
+
p1_x, p1_y, p1_z,
|
|
73
|
+
box_x0, box_y0, box_z0,
|
|
74
|
+
box_x1, box_y1, box_z1
|
|
75
|
+
) {
|
|
76
|
+
const d_x = p1_x - p0_x;
|
|
77
|
+
const d_y = p1_y - p0_y;
|
|
78
|
+
const d_z = p1_z - p0_z;
|
|
79
|
+
|
|
80
|
+
if (d_x === 0 && d_y === 0 && d_z === 0) {
|
|
81
|
+
// Degenerate segment (a point) — its parameter is arbitrary.
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
_a_t = -1; _a_g = 0;
|
|
86
|
+
_b_t = 2; _b_g = 0;
|
|
87
|
+
_c_t = -1; _c_g = 0;
|
|
88
|
+
_d_t = 2; _d_g = 0;
|
|
89
|
+
|
|
90
|
+
// Endpoint candidates.
|
|
91
|
+
_consider(0, v3_dot(
|
|
92
|
+
p0_x - clamp(p0_x, box_x0, box_x1),
|
|
93
|
+
p0_y - clamp(p0_y, box_y0, box_y1),
|
|
94
|
+
p0_z - clamp(p0_z, box_z0, box_z1),
|
|
95
|
+
d_x, d_y, d_z
|
|
96
|
+
));
|
|
97
|
+
_consider(1, v3_dot(
|
|
98
|
+
p1_x - clamp(p1_x, box_x0, box_x1),
|
|
99
|
+
p1_y - clamp(p1_y, box_y0, box_y1),
|
|
100
|
+
p1_z - clamp(p1_z, box_z0, box_z1),
|
|
101
|
+
d_x, d_y, d_z
|
|
102
|
+
));
|
|
103
|
+
|
|
104
|
+
// Face-plane crossing candidates (where an axis enters/leaves its slab).
|
|
105
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
106
|
+
const d_a = axis === 0 ? d_x : (axis === 1 ? d_y : d_z);
|
|
107
|
+
if (d_a === 0) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const p0_a = axis === 0 ? p0_x : (axis === 1 ? p0_y : p0_z);
|
|
111
|
+
const lo_a = axis === 0 ? box_x0 : (axis === 1 ? box_y0 : box_z0);
|
|
112
|
+
const hi_a = axis === 0 ? box_x1 : (axis === 1 ? box_y1 : box_z1);
|
|
113
|
+
const inv_d = 1 / d_a;
|
|
114
|
+
|
|
115
|
+
for (let side = 0; side < 2; side++) {
|
|
116
|
+
const tc = ((side === 0 ? lo_a : hi_a) - p0_a) * inv_d;
|
|
117
|
+
if (tc <= 0 || tc >= 1) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const c_x = p0_x + d_x * tc;
|
|
121
|
+
const c_y = p0_y + d_y * tc;
|
|
122
|
+
const c_z = p0_z + d_z * tc;
|
|
123
|
+
_consider(tc, v3_dot(
|
|
124
|
+
c_x - clamp(c_x, box_x0, box_x1),
|
|
125
|
+
c_y - clamp(c_y, box_y0, box_y1),
|
|
126
|
+
c_z - clamp(c_z, box_z0, box_z1),
|
|
127
|
+
d_x, d_y, d_z
|
|
128
|
+
));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Rightmost minimiser: where g' last reaches 0 (g' linear between a and b).
|
|
133
|
+
let t_hi;
|
|
134
|
+
if (_a_t < 0) {
|
|
135
|
+
t_hi = 0; // g' > 0 everywhere → minimum at the p0 end
|
|
136
|
+
} else if (_b_t > 1) {
|
|
137
|
+
t_hi = 1; // g' ≤ 0 everywhere → minimum at the p1 end
|
|
138
|
+
} else {
|
|
139
|
+
t_hi = _a_t - _a_g * (_b_t - _a_t) / (_b_g - _a_g);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Leftmost minimiser: where g' first reaches 0.
|
|
143
|
+
let t_lo;
|
|
144
|
+
if (_d_t > 1) {
|
|
145
|
+
t_lo = 1; // g' < 0 everywhere → minimum at the p1 end
|
|
146
|
+
} else if (_c_t < 0) {
|
|
147
|
+
t_lo = 0; // g' ≥ 0 everywhere → minimum at the p0 end
|
|
148
|
+
} else {
|
|
149
|
+
t_lo = _c_t - _c_g * (_d_t - _c_t) / (_d_g - _c_g);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return (t_lo + t_hi) * 0.5;
|
|
153
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BinaryTopology.d.ts","sourceRoot":"","sources":["../../../../../../../../src/core/geom/3d/topology/struct/binary/BinaryTopology.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"BinaryTopology.d.ts","sourceRoot":"","sources":["../../../../../../../../src/core/geom/3d/topology/struct/binary/BinaryTopology.js"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { assert } from "../../../../../assert.js";
|
|
2
|
-
import { array_copy } from "../../../../../collection/array/array_copy.js";
|
|
3
2
|
import { BinaryElementPool } from "./BinaryElementPool.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -358,7 +357,9 @@ export class BinaryTopology {
|
|
|
358
357
|
|
|
359
358
|
const v_address = pool.element_address(id);
|
|
360
359
|
const v_offset = v_address >> 2; // get 4-byte boundary
|
|
361
|
-
|
|
360
|
+
result[result_offset] = pool.data_float32[v_offset];
|
|
361
|
+
result[result_offset + 1] = pool.data_float32[v_offset + 1];
|
|
362
|
+
result[result_offset + 2] = pool.data_float32[v_offset + 2];
|
|
362
363
|
}
|
|
363
364
|
|
|
364
365
|
/**
|
|
@@ -372,7 +373,9 @@ export class BinaryTopology {
|
|
|
372
373
|
|
|
373
374
|
const v_address = pool.element_address(id);
|
|
374
375
|
const v_offset = v_address >> 2; // get 4-byte boundary
|
|
375
|
-
|
|
376
|
+
pool.data_float32[v_offset] = value[value_offset];
|
|
377
|
+
pool.data_float32[v_offset + 1] = value[value_offset + 1];
|
|
378
|
+
pool.data_float32[v_offset + 2] = value[value_offset + 2];
|
|
376
379
|
}
|
|
377
380
|
|
|
378
381
|
/**
|
|
@@ -386,7 +389,9 @@ export class BinaryTopology {
|
|
|
386
389
|
|
|
387
390
|
const v_address = pool.element_address(id) + 3 * FLOAT_32_SIZE;
|
|
388
391
|
const v_offset = v_address >> 2; // get 4-byte boundary
|
|
389
|
-
|
|
392
|
+
result[result_offset] = pool.data_float32[v_offset];
|
|
393
|
+
result[result_offset + 1] = pool.data_float32[v_offset + 1];
|
|
394
|
+
result[result_offset + 2] = pool.data_float32[v_offset + 2];
|
|
390
395
|
}
|
|
391
396
|
|
|
392
397
|
/**
|
|
@@ -400,7 +405,9 @@ export class BinaryTopology {
|
|
|
400
405
|
|
|
401
406
|
const v_address = pool.element_address(id) + 3 * FLOAT_32_SIZE;
|
|
402
407
|
const v_offset = v_address >> 2; // get 4-byte boundary
|
|
403
|
-
|
|
408
|
+
pool.data_float32[v_offset] = value[value_offset];
|
|
409
|
+
pool.data_float32[v_offset + 1] = value[value_offset + 1];
|
|
410
|
+
pool.data_float32[v_offset + 2] = value[value_offset + 2];
|
|
404
411
|
}
|
|
405
412
|
|
|
406
413
|
/**
|
|
@@ -891,7 +898,9 @@ export class BinaryTopology {
|
|
|
891
898
|
// Offset by the first loop pointer (UINT_32_SIZE)
|
|
892
899
|
const f_address = pool.element_address(id) + UINT_32_SIZE;
|
|
893
900
|
const f_offset = f_address >> 2;
|
|
894
|
-
|
|
901
|
+
result[result_offset] = pool.data_float32[f_offset];
|
|
902
|
+
result[result_offset + 1] = pool.data_float32[f_offset + 1];
|
|
903
|
+
result[result_offset + 2] = pool.data_float32[f_offset + 2];
|
|
895
904
|
}
|
|
896
905
|
|
|
897
906
|
/**
|
|
@@ -903,7 +912,9 @@ export class BinaryTopology {
|
|
|
903
912
|
const pool = this.__face_pool;
|
|
904
913
|
const f_address = pool.element_address(id) + UINT_32_SIZE;
|
|
905
914
|
const f_offset = f_address >> 2;
|
|
906
|
-
|
|
915
|
+
pool.data_float32[f_offset] = value[value_offset];
|
|
916
|
+
pool.data_float32[f_offset + 1] = value[value_offset + 1];
|
|
917
|
+
pool.data_float32[f_offset + 2] = value[value_offset + 2];
|
|
907
918
|
}
|
|
908
919
|
|
|
909
920
|
/**
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
* Call AFTER an initial vertex-merge/edge-fuse, then merge + fuse again so the new split vertices weld
|
|
12
12
|
* onto the T-junction vertices and the resulting duplicate edges fuse.
|
|
13
13
|
*
|
|
14
|
+
* A BVH over the vertices replaces the naive all-vertices-per-edge scan: for each boundary edge we
|
|
15
|
+
* query only the vertices within the tolerance of the edge segment (a capsule), which IS the on-edge
|
|
16
|
+
* region, so no separate per-candidate distance test is needed. The BVH is built ONCE -- every split
|
|
17
|
+
* adds a vertex snapped exactly onto an existing one's position, so no candidate position is ever
|
|
18
|
+
* missing from the original leaf set, and the tree never needs rebuilding as edges split.
|
|
19
|
+
*
|
|
14
20
|
* @param {BinaryTopology} mesh
|
|
15
21
|
* @param {number} [tolerance] max distance from a vertex to an edge for it to count as "on" the edge
|
|
16
22
|
* @returns {number} number of splits performed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bt_mesh_resolve_t_junctions.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/geom/3d/topology/struct/binary/io/bt_mesh_resolve_t_junctions.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bt_mesh_resolve_t_junctions.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/geom/3d/topology/struct/binary/io/bt_mesh_resolve_t_junctions.js"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,8EAHW,MAAM,GACJ,MAAM,CAwGlB"}
|
|
@@ -1,95 +1,139 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
1
|
+
import { BVH } from "../../../../../../bvh2/bvh3/BVH.js";
|
|
2
|
+
import {
|
|
3
|
+
bvh_query_user_data_overlaps_capsule
|
|
4
|
+
} from "../../../../../../bvh2/bvh3/query/bvh_query_user_data_overlaps_capsule.js";
|
|
5
|
+
import { NULL_POINTER } from "../BinaryTopology.js";
|
|
6
|
+
import { bt_edge_split } from "./edge/bt_edge_split.js";
|
|
7
|
+
|
|
8
|
+
const _a = new Float32Array(3);
|
|
9
|
+
const _b = new Float32Array(3);
|
|
10
|
+
const _p = new Float32Array(3);
|
|
11
|
+
const _hit = new Float32Array(3);
|
|
12
|
+
const _capsule = new Float32Array(7);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolve T-junctions: where a vertex lies on the interior of a boundary edge (rather than at one of
|
|
16
|
+
* its endpoints), split that edge at the vertex so the two pieces can later be welded/fused into a
|
|
17
|
+
* shared edge.
|
|
18
|
+
*
|
|
19
|
+
* This is required for meshes assembled from independently-authored faces of differing sizes - e.g. an
|
|
20
|
+
* L-shaped or ring floor built from rectangles, where one rectangle's corner falls in the middle of a
|
|
21
|
+
* neighbour's edge. Without it those faces touch only at a single vertex, so edge-based neighbour
|
|
22
|
+
* queries (island detection, erosion, path-finding) treat them as DISCONNECTED.
|
|
23
|
+
*
|
|
24
|
+
* Call AFTER an initial vertex-merge/edge-fuse, then merge + fuse again so the new split vertices weld
|
|
25
|
+
* onto the T-junction vertices and the resulting duplicate edges fuse.
|
|
26
|
+
*
|
|
27
|
+
* A BVH over the vertices replaces the naive all-vertices-per-edge scan: for each boundary edge we
|
|
28
|
+
* query only the vertices within the tolerance of the edge segment (a capsule), which IS the on-edge
|
|
29
|
+
* region, so no separate per-candidate distance test is needed. The BVH is built ONCE -- every split
|
|
30
|
+
* adds a vertex snapped exactly onto an existing one's position, so no candidate position is ever
|
|
31
|
+
* missing from the original leaf set, and the tree never needs rebuilding as edges split.
|
|
32
|
+
*
|
|
33
|
+
* @param {BinaryTopology} mesh
|
|
34
|
+
* @param {number} [tolerance] max distance from a vertex to an edge for it to count as "on" the edge
|
|
35
|
+
* @returns {number} number of splits performed
|
|
36
|
+
*/
|
|
37
|
+
export function bt_mesh_resolve_t_junctions(mesh, tolerance = 1e-4) {
|
|
38
|
+
const tol_sq = tolerance * tolerance;
|
|
39
|
+
|
|
40
|
+
// --- Build a vertex BVH over the original vertices (point leaves; user data = vertex id) ---
|
|
41
|
+
const vertices = mesh.vertices;
|
|
42
|
+
const vertex_count = vertices.size;
|
|
43
|
+
|
|
44
|
+
const bvh = new BVH();
|
|
45
|
+
|
|
46
|
+
let leaf_count = 0;
|
|
47
|
+
for (let v = 0; v < vertex_count; v++) {
|
|
48
|
+
if (!vertices.is_allocated(v)) continue;
|
|
49
|
+
|
|
50
|
+
mesh.vertex_read_coordinate(_p, 0, v);
|
|
51
|
+
|
|
52
|
+
const node = bvh.allocate_node();
|
|
53
|
+
bvh.node_set_user_data(node, v);
|
|
54
|
+
bvh.node_set_aabb_primitive(node, _p[0], _p[1], _p[2], _p[0], _p[1], _p[2]);
|
|
55
|
+
bvh.insert_leaf(node);
|
|
56
|
+
|
|
57
|
+
leaf_count++;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (leaf_count === 0) {
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The query writes one vertex id per overlapping leaf; it can never exceed the leaf count.
|
|
65
|
+
const candidates = new Uint32Array(leaf_count);
|
|
66
|
+
|
|
67
|
+
let total = 0;
|
|
68
|
+
let rounds = 0;
|
|
69
|
+
let changed = true;
|
|
70
|
+
|
|
71
|
+
while (changed && rounds < 256) {
|
|
72
|
+
changed = false;
|
|
73
|
+
rounds++;
|
|
74
|
+
|
|
75
|
+
const edge_count = mesh.edges.size;
|
|
76
|
+
|
|
77
|
+
for (let e = 0; e < edge_count; e++) {
|
|
78
|
+
if (!mesh.edges.is_allocated(e)) continue;
|
|
79
|
+
|
|
80
|
+
// only boundary edges (a single radial loop) can carry a T-junction
|
|
81
|
+
const l = mesh.edge_read_loop(e);
|
|
82
|
+
if (l === NULL_POINTER) continue;
|
|
83
|
+
if (mesh.loop_read_radial_next(l) !== l) continue;
|
|
84
|
+
|
|
85
|
+
const v1 = mesh.edge_read_vertex1(e);
|
|
86
|
+
const v2 = mesh.edge_read_vertex2(e);
|
|
87
|
+
|
|
88
|
+
mesh.vertex_read_coordinate(_a, 0, v1);
|
|
89
|
+
mesh.vertex_read_coordinate(_b, 0, v2);
|
|
90
|
+
|
|
91
|
+
const abx = _b[0] - _a[0], aby = _b[1] - _a[1], abz = _b[2] - _a[2];
|
|
92
|
+
const ab_sq = abx * abx + aby * aby + abz * abz;
|
|
93
|
+
if (ab_sq <= tol_sq) continue;
|
|
94
|
+
|
|
95
|
+
const t_margin = tolerance / Math.sqrt(ab_sq);
|
|
96
|
+
|
|
97
|
+
// candidate vertices: those within `tolerance` of the edge segment (a capsule)
|
|
98
|
+
_capsule[0] = _a[0]; _capsule[1] = _a[1]; _capsule[2] = _a[2];
|
|
99
|
+
_capsule[3] = _b[0]; _capsule[4] = _b[1]; _capsule[5] = _b[2];
|
|
100
|
+
_capsule[6] = tolerance;
|
|
101
|
+
|
|
102
|
+
const candidate_count = bvh_query_user_data_overlaps_capsule(candidates, 0, bvh, _capsule);
|
|
103
|
+
|
|
104
|
+
let split_t = -1;
|
|
105
|
+
let split_v = -1;
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < candidate_count; i++) {
|
|
108
|
+
const v = candidates[i];
|
|
109
|
+
|
|
110
|
+
// Candidates are original vertices, which are never removed during resolution (splits
|
|
111
|
+
// only add), so they are always allocated -- no is_allocated check needed.
|
|
112
|
+
if (v === v1 || v === v2) continue;
|
|
113
|
+
|
|
114
|
+
mesh.vertex_read_coordinate(_p, 0, v);
|
|
115
|
+
|
|
116
|
+
// the capsule query already guarantees distance-to-segment <= tolerance; all that is
|
|
117
|
+
// left is to exclude the segment's endpoints (its hemispherical end caps)
|
|
118
|
+
const t = ((_p[0] - _a[0]) * abx + (_p[1] - _a[1]) * aby + (_p[2] - _a[2]) * abz) / ab_sq;
|
|
119
|
+
if (t <= t_margin || t >= 1 - t_margin) continue;
|
|
120
|
+
|
|
121
|
+
split_t = t;
|
|
122
|
+
split_v = v;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (split_t >= 0) {
|
|
127
|
+
// snapshot the on-edge vertex coordinate, split, then snap the new vertex exactly onto it
|
|
128
|
+
mesh.vertex_read_coordinate(_hit, 0, split_v);
|
|
129
|
+
const nv = bt_edge_split(mesh, e, split_t);
|
|
130
|
+
mesh.vertex_write_coordinate(nv, _hit, 0);
|
|
131
|
+
|
|
132
|
+
total++;
|
|
133
|
+
changed = true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return total;
|
|
139
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bt_mesh_simplify.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bt_mesh_simplify.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js"],"names":[],"mappings":"AA6JA;;;;;;;;;;;;;;GAcG;AACH,0EAHW,MAAM,wBACN,IAAI,MAAM,CAAC,QA6HrB"}
|
|
@@ -7,6 +7,7 @@ import { bt_mesh_compute_vertex_quadratics } from "./bt_mesh_compute_vertex_quad
|
|
|
7
7
|
import { bt_edge_collapse } from "./edge/bt_edge_collapse.js";
|
|
8
8
|
import { bt_edge_kill } from "./edge/bt_edge_kill.js";
|
|
9
9
|
import { bt_edge_kill_parallels } from "./edge/bt_edge_kill_parallels.js";
|
|
10
|
+
import { bt_edge_swap_vertex_slots } from "./edge/bt_edge_swap_vertex_slots.js";
|
|
10
11
|
import { bt_vert_fuse_duplicate_edges } from "./vertex/bt_vert_fuse_duplicate_edges.js";
|
|
11
12
|
import { bt_vert_kill } from "./vertex/bt_vert_kill.js";
|
|
12
13
|
|
|
@@ -50,31 +51,6 @@ function count_edge_faces(mesh, edge_id) {
|
|
|
50
51
|
return count;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
/**
|
|
54
|
-
* Swap the v1/v2 slots of an edge. Disk cycle pointers for the two slots are swapped
|
|
55
|
-
* alongside so that the disk cycles on both attached vertices remain valid.
|
|
56
|
-
*
|
|
57
|
-
* @param {BinaryTopology} mesh
|
|
58
|
-
* @param {number} edge_id
|
|
59
|
-
*/
|
|
60
|
-
function swap_edge_vertex_slots(mesh, edge_id) {
|
|
61
|
-
const v1 = mesh.edge_read_vertex1(edge_id);
|
|
62
|
-
const v2 = mesh.edge_read_vertex2(edge_id);
|
|
63
|
-
|
|
64
|
-
const v1_next = mesh.edge_read_v1_disk_next(edge_id);
|
|
65
|
-
const v1_prev = mesh.edge_read_v1_disk_prev(edge_id);
|
|
66
|
-
const v2_next = mesh.edge_read_v2_disk_next(edge_id);
|
|
67
|
-
const v2_prev = mesh.edge_read_v2_disk_prev(edge_id);
|
|
68
|
-
|
|
69
|
-
mesh.edge_write_vertex1(edge_id, v2);
|
|
70
|
-
mesh.edge_write_vertex2(edge_id, v1);
|
|
71
|
-
|
|
72
|
-
mesh.edge_write_v1_disk_next(edge_id, v2_next);
|
|
73
|
-
mesh.edge_write_v1_disk_prev(edge_id, v2_prev);
|
|
74
|
-
mesh.edge_write_v2_disk_next(edge_id, v1_next);
|
|
75
|
-
mesh.edge_write_v2_disk_prev(edge_id, v1_prev);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
54
|
/**
|
|
79
55
|
* Evaluate a potential edge collapse: choose the optimal surviving vertex position
|
|
80
56
|
* and return the quadric-based cost. Returns Infinity if the edge cannot be collapsed.
|
|
@@ -285,7 +261,7 @@ export function bt_mesh_simplify(
|
|
|
285
261
|
// bt_edge_collapse keeps v1 as the survivor. If v2 is the pinned endpoint,
|
|
286
262
|
// swap the edge's slots so the pinned vertex ends up in the v1 position.
|
|
287
263
|
if (v2_restricted) {
|
|
288
|
-
|
|
264
|
+
bt_edge_swap_vertex_slots(mesh, edge_id);
|
|
289
265
|
}
|
|
290
266
|
|
|
291
267
|
const pre_survivor = mesh.edge_read_vertex1(edge_id);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clean up a triangle mesh in place by removing only geometrically redundant vertices, so the
|
|
3
|
+
* surface and its boundary are preserved to within `max_error`. Unlike {@link bt_mesh_simplify}
|
|
4
|
+
* (quadric LoD decimation driven to a face-count target, free to relocate vertices), this is a
|
|
5
|
+
* conservative half-edge cleanup: it never moves a surviving vertex and never changes the
|
|
6
|
+
* boundary outline or hole topology by more than `max_error`. It removes the dense interior of
|
|
7
|
+
* flat/near-coplanar regions and collinear runs of boundary vertices, and stops once the
|
|
8
|
+
* cheapest remaining collapse would exceed the tolerance.
|
|
9
|
+
*
|
|
10
|
+
* Intended for navmesh post-processing, where the input is a walkable surface whose outline and
|
|
11
|
+
* holes (obstacle footprints) carry meaning and must not drift, but whose interior inherits far
|
|
12
|
+
* more triangles than navigation needs.
|
|
13
|
+
*
|
|
14
|
+
* @param {BinaryTopology} mesh Mesh to clean up in-place.
|
|
15
|
+
* @param {number} max_error Maximum world-space deviation allowed for any removed vertex and for
|
|
16
|
+
* the boundary polyline. A no-op when <= 0.
|
|
17
|
+
*/
|
|
18
|
+
export function bt_mesh_simplify_by_error(mesh: BinaryTopology, max_error: number): void;
|
|
19
|
+
//# sourceMappingURL=bt_mesh_simplify_by_error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bt_mesh_simplify_by_error.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify_by_error.js"],"names":[],"mappings":"AA4aA;;;;;;;;;;;;;;;;GAgBG;AACH,2EAHW,MAAM,QAgHhB"}
|