@woosh/meep-engine 2.163.9 → 2.163.10
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/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/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/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
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"description": "Pure JavaScript game engine. Fully featured and production ready.",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"author": "Alexander Goldring",
|
|
9
|
-
"version": "2.163.
|
|
9
|
+
"version": "2.163.10",
|
|
10
10
|
"main": "build/meep.module.js",
|
|
11
11
|
"module": "build/meep.module.js",
|
|
12
12
|
"exports": {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IndexedFloatMaxHeap.d.ts","sourceRoot":"","sources":["../../../../../src/core/collection/heap/IndexedFloatMaxHeap.js"],"names":[],"mappings":"AA6BA;IACI;;;OAGG;IACH,yBAHW,MAAM,0BACN,MAAM,EAsBhB;IAbG,wBAA4C;IAC5C,eAAe;IAEf,2BAA4E;IAC5E,2BAAwD;IACxD,6BAA0D;IAE1D;;;OAGG;IACH,cAFU,WAAW,CAE2B;IAIpD,mBAEC;IAED;;;OAGG;IACH,cAQC;IAED,2BAEC;IAED;;;OAGG;IACH,qBAkBC;IAED;;;;OAIG;IACH,oBAsBC;IAED;;;;OAIG;IACH,kBAYC;IAED,wBAUC;IAED;;;OAGG;IACH,WAHW,MAAM,SACN,MAAM,QAehB;IAED;;OAEG;IACH,WAFa,MAAM,CAuBlB;IAED;;;OAGG;IACH,WAHW,MAAM,aACN,MAAM,QAehB;IAED;;;OAGG;IACH,WAHW,MAAM,GACJ,OAAO,CA+BnB;CACJ"}
|
|
@@ -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"}
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { assert } from "../../../../../../assert.js";
|
|
2
|
+
import { FibonacciHeap } from "../../../../../../collection/heap/FibonacciHeap.js";
|
|
3
|
+
import {
|
|
4
|
+
line3_compute_segment_point_distance_sqr
|
|
5
|
+
} from "../../../../line/line3_compute_segment_point_distance_sqr.js";
|
|
6
|
+
import { NULL_POINTER } from "../BinaryTopology.js";
|
|
7
|
+
import { bt_query_edge_is_boundary } from "../query/bt_query_edge_is_boundary.js";
|
|
8
|
+
import { bt_mesh_cleanup_faceless_references } from "./bt_mesh_cleanup_faceless_references.js";
|
|
9
|
+
import { bt_mesh_compute_vertex_quadratics } from "./bt_mesh_compute_vertex_quadratics.js";
|
|
10
|
+
import { bt_edge_collapse } from "./edge/bt_edge_collapse.js";
|
|
11
|
+
import { bt_edge_kill_parallels } from "./edge/bt_edge_kill_parallels.js";
|
|
12
|
+
import { bt_edge_swap_vertex_slots } from "./edge/bt_edge_swap_vertex_slots.js";
|
|
13
|
+
import { bt_vert_fuse_duplicate_edges } from "./vertex/bt_vert_fuse_duplicate_edges.js";
|
|
14
|
+
|
|
15
|
+
// Module-level scratch to keep the hot loop allocation-free.
|
|
16
|
+
const _pos_a = new Float32Array(3);
|
|
17
|
+
const _pos_b = new Float32Array(3);
|
|
18
|
+
const _pos_c = new Float32Array(3);
|
|
19
|
+
const _pos_s = new Float32Array(3);
|
|
20
|
+
const _pos_v = new Float32Array(3);
|
|
21
|
+
const _pos_target = new Float32Array(3);
|
|
22
|
+
const _n_before = new Float32Array(3);
|
|
23
|
+
const _n_after = new Float32Array(3);
|
|
24
|
+
|
|
25
|
+
// Cap on the merged valence (deg(v1) + deg(v2)) of a collapse. A flat region otherwise lets the
|
|
26
|
+
// greedy order funnel every triangle into a single high-valence "star" vertex, which makes each
|
|
27
|
+
// subsequent disk walk / requeue O(degree) and the whole pass O(n^2). Bounding the merged
|
|
28
|
+
// neighbourhood keeps every per-vertex walk O(1), so the pass is O(n log n). It can leave a few
|
|
29
|
+
// extra triangles in a large flat region versus the theoretical minimum -- a good trade for
|
|
30
|
+
// navigation, where a balanced low-valence mesh also searches better than a sliver fan.
|
|
31
|
+
const DEGREE_SUM_CAP = 24;
|
|
32
|
+
|
|
33
|
+
// Per-vertex degree and boundary-membership caches, kept in plain typed arrays so choose_collapse
|
|
34
|
+
// reads them in O(1) instead of disk-walking on every call (BinaryTopology accessors go through a
|
|
35
|
+
// DataView, which is comparatively slow, and choose_collapse runs far more often than collapses do).
|
|
36
|
+
// They are refreshed for the survivor and its neighbours after each collapse -- the only vertices a
|
|
37
|
+
// collapse can change. Assigned per call to bt_mesh_simplify_by_error (the pass is not re-entrant).
|
|
38
|
+
let _degree = null;
|
|
39
|
+
let _vboundary = null;
|
|
40
|
+
|
|
41
|
+
// Result of the most recent choose_collapse: the kept and removed endpoints of the best
|
|
42
|
+
// (cheapest, in-tolerance) collapse direction. Only meaningful when it returned a finite cost.
|
|
43
|
+
let _choice_survivor = NULL_POINTER;
|
|
44
|
+
let _choice_victim = NULL_POINTER;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Refresh `_degree[v]` and `_vboundary[v]` from the mesh in a single disk-cycle walk: the degree is
|
|
48
|
+
* the cycle length and the vertex is on a boundary when any incident edge is a boundary edge.
|
|
49
|
+
*
|
|
50
|
+
* @param {BinaryTopology} mesh
|
|
51
|
+
* @param {number} v
|
|
52
|
+
*/
|
|
53
|
+
function recompute_vertex_cache(mesh, v) {
|
|
54
|
+
let e = mesh.vertex_read_edge(v);
|
|
55
|
+
|
|
56
|
+
if (e === NULL_POINTER) {
|
|
57
|
+
_degree[v] = 0;
|
|
58
|
+
_vboundary[v] = 0;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const start_e = e;
|
|
63
|
+
let n = 0;
|
|
64
|
+
let bnd = 0;
|
|
65
|
+
|
|
66
|
+
do {
|
|
67
|
+
n++;
|
|
68
|
+
|
|
69
|
+
if (bnd === 0 && bt_query_edge_is_boundary(mesh, e)) {
|
|
70
|
+
bnd = 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
e = (mesh.edge_read_vertex1(e) === v)
|
|
74
|
+
? mesh.edge_read_v1_disk_next(e)
|
|
75
|
+
: mesh.edge_read_v2_disk_next(e);
|
|
76
|
+
} while (e !== start_e && e !== NULL_POINTER);
|
|
77
|
+
|
|
78
|
+
_degree[v] = n;
|
|
79
|
+
_vboundary[v] = bnd;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* For a boundary vertex `v`, return the far endpoint of its boundary edge other than
|
|
84
|
+
* `exclude_edge` -- i.e. `v`'s neighbour along the boundary polyline on the far side.
|
|
85
|
+
*
|
|
86
|
+
* Returns NULL_POINTER unless `v` has exactly one such other boundary edge (a clean
|
|
87
|
+
* 2-manifold boundary vertex). Dead-ends and pinch/junction vertices are declined so the
|
|
88
|
+
* caller conservatively skips simplifying there rather than mis-stepping the polyline.
|
|
89
|
+
*
|
|
90
|
+
* @param {BinaryTopology} mesh
|
|
91
|
+
* @param {number} v
|
|
92
|
+
* @param {number} exclude_edge
|
|
93
|
+
* @returns {number}
|
|
94
|
+
*/
|
|
95
|
+
function other_boundary_neighbor(mesh, v, exclude_edge) {
|
|
96
|
+
let e = mesh.vertex_read_edge(v);
|
|
97
|
+
|
|
98
|
+
if (e === NULL_POINTER) {
|
|
99
|
+
return NULL_POINTER;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const start_e = e;
|
|
103
|
+
|
|
104
|
+
let found = NULL_POINTER;
|
|
105
|
+
let count = 0;
|
|
106
|
+
|
|
107
|
+
do {
|
|
108
|
+
if (e !== exclude_edge && bt_query_edge_is_boundary(mesh, e)) {
|
|
109
|
+
count++;
|
|
110
|
+
const v1 = mesh.edge_read_vertex1(e);
|
|
111
|
+
const v2 = mesh.edge_read_vertex2(e);
|
|
112
|
+
found = (v1 === v) ? v2 : v1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
e = (mesh.edge_read_vertex1(e) === v)
|
|
116
|
+
? mesh.edge_read_v1_disk_next(e)
|
|
117
|
+
: mesh.edge_read_v2_disk_next(e);
|
|
118
|
+
} while (e !== start_e && e !== NULL_POINTER);
|
|
119
|
+
|
|
120
|
+
if (count !== 1) {
|
|
121
|
+
return NULL_POINTER;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return found;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Unnormalised triangle normal (b-a) x (c-a) written into `out`.
|
|
129
|
+
*
|
|
130
|
+
* Deliberately NOT v3_compute_triangle_normal: the flip guard needs the raw cross product. Its
|
|
131
|
+
* magnitude vanishing signals a degenerate (zero-area) result, which the caller's `dot <= 0` test
|
|
132
|
+
* then rejects. v3_compute_triangle_normal normalises (discarding that magnitude) and returns a
|
|
133
|
+
* fixed (0,1,0) for degenerate triangles, which would let a collapse-to-sliver slip the guard.
|
|
134
|
+
*/
|
|
135
|
+
function triangle_cross(out, ax, ay, az, bx, by, bz, cx, cy, cz) {
|
|
136
|
+
const ux = bx - ax, uy = by - ay, uz = bz - az;
|
|
137
|
+
const vx = cx - ax, vy = cy - ay, vz = cz - az;
|
|
138
|
+
|
|
139
|
+
out[0] = uy * vz - uz * vy;
|
|
140
|
+
out[1] = uz * vx - ux * vz;
|
|
141
|
+
out[2] = ux * vy - uy * vx;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Quadric-error cost of relocating both endpoints' supporting planes to `(tx,ty,tz)`.
|
|
146
|
+
* For a half-edge collapse the target is the (stationary) survivor's own position, so this
|
|
147
|
+
* measures how far the removed vertex's surface would deviate -- zero on a flat region.
|
|
148
|
+
*/
|
|
149
|
+
function quadric_cost_at(q_survivor, q_victim, tx, ty, tz) {
|
|
150
|
+
return q_survivor.evaluate(tx, ty, tz) + q_victim.evaluate(tx, ty, tz);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Pick the best allowed collapse for edge `e` and return its quadric error. Writes the kept
|
|
155
|
+
* and removed vertices into `_choice_survivor` / `_choice_victim`. Returns POSITIVE_INFINITY
|
|
156
|
+
* when no direction is allowed.
|
|
157
|
+
*
|
|
158
|
+
* Allowability rules (cleanup, not decimation -- nothing moves, the boundary shape is kept):
|
|
159
|
+
* - the survivor is always an existing vertex and never relocates;
|
|
160
|
+
* - a *boundary* edge may collapse only by removing an endpoint that is collinear within
|
|
161
|
+
* `eps` with its two boundary neighbours, so the outline polyline shifts by <= eps;
|
|
162
|
+
* - an *interior* edge may remove only a non-boundary endpoint (the boundary endpoint, if
|
|
163
|
+
* any, survives), and is forbidden outright when *both* endpoints are boundary vertices
|
|
164
|
+
* (collapsing it would pinch a thin neck or weld two boundary loops -- a topology change).
|
|
165
|
+
*
|
|
166
|
+
* @param {BinaryTopology} mesh
|
|
167
|
+
* @param {number} e
|
|
168
|
+
* @param {Quadric3[]} quadratics
|
|
169
|
+
* @param {number} eps
|
|
170
|
+
* @returns {number}
|
|
171
|
+
*/
|
|
172
|
+
function choose_collapse(mesh, e, quadratics, eps) {
|
|
173
|
+
const v1 = mesh.edge_read_vertex1(e);
|
|
174
|
+
const v2 = mesh.edge_read_vertex2(e);
|
|
175
|
+
|
|
176
|
+
if (v1 === v2) {
|
|
177
|
+
return Number.POSITIVE_INFINITY;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (mesh.edge_read_loop(e) === NULL_POINTER) {
|
|
181
|
+
// wire edge with no incident face -- nothing to clean up
|
|
182
|
+
return Number.POSITIVE_INFINITY;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const q1 = quadratics[v1];
|
|
186
|
+
const q2 = quadratics[v2];
|
|
187
|
+
|
|
188
|
+
if (q1 == null || q2 == null) {
|
|
189
|
+
return Number.POSITIVE_INFINITY;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Valence cap (see DEGREE_SUM_CAP). The merged neighbourhood size is the same whichever
|
|
193
|
+
// endpoint survives, so this gates the edge regardless of collapse direction.
|
|
194
|
+
if (_degree[v1] + _degree[v2] > DEGREE_SUM_CAP) {
|
|
195
|
+
return Number.POSITIVE_INFINITY;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
mesh.vertex_read_coordinate(_pos_a, 0, v1);
|
|
199
|
+
mesh.vertex_read_coordinate(_pos_b, 0, v2);
|
|
200
|
+
|
|
201
|
+
let best = Number.POSITIVE_INFINITY;
|
|
202
|
+
let best_survivor = NULL_POINTER;
|
|
203
|
+
let best_victim = NULL_POINTER;
|
|
204
|
+
|
|
205
|
+
if (bt_query_edge_is_boundary(mesh, e)) {
|
|
206
|
+
// Boundary edge: both endpoints are boundary vertices. Removing a vertex replaces the two
|
|
207
|
+
// boundary edges meeting at it with the single segment between its two boundary neighbours,
|
|
208
|
+
// so the outline shifts by the removed vertex's distance to that *segment*. Allow the
|
|
209
|
+
// removal only when that shift is within eps. Distance must be to the segment, not the
|
|
210
|
+
// infinite line: a near-collinear spike whose tip projects beyond its neighbours sits on
|
|
211
|
+
// their line yet is far from the segment between them, and cutting it would move the
|
|
212
|
+
// boundary by the whole spike length.
|
|
213
|
+
const eps_sq = eps * eps;
|
|
214
|
+
|
|
215
|
+
// remove v2, keep v1
|
|
216
|
+
const w2 = other_boundary_neighbor(mesh, v2, e);
|
|
217
|
+
if (w2 !== NULL_POINTER) {
|
|
218
|
+
mesh.vertex_read_coordinate(_pos_c, 0, w2);
|
|
219
|
+
const dev_sqr = line3_compute_segment_point_distance_sqr(
|
|
220
|
+
_pos_a[0], _pos_a[1], _pos_a[2],
|
|
221
|
+
_pos_c[0], _pos_c[1], _pos_c[2],
|
|
222
|
+
_pos_b[0], _pos_b[1], _pos_b[2]
|
|
223
|
+
);
|
|
224
|
+
if (dev_sqr <= eps_sq) {
|
|
225
|
+
const cost = quadric_cost_at(q1, q2, _pos_a[0], _pos_a[1], _pos_a[2]);
|
|
226
|
+
if (cost < best) {
|
|
227
|
+
best = cost;
|
|
228
|
+
best_survivor = v1;
|
|
229
|
+
best_victim = v2;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// remove v1, keep v2
|
|
235
|
+
const w1 = other_boundary_neighbor(mesh, v1, e);
|
|
236
|
+
if (w1 !== NULL_POINTER) {
|
|
237
|
+
mesh.vertex_read_coordinate(_pos_c, 0, w1);
|
|
238
|
+
const dev_sqr = line3_compute_segment_point_distance_sqr(
|
|
239
|
+
_pos_b[0], _pos_b[1], _pos_b[2],
|
|
240
|
+
_pos_c[0], _pos_c[1], _pos_c[2],
|
|
241
|
+
_pos_a[0], _pos_a[1], _pos_a[2]
|
|
242
|
+
);
|
|
243
|
+
if (dev_sqr <= eps_sq) {
|
|
244
|
+
const cost = quadric_cost_at(q1, q2, _pos_b[0], _pos_b[1], _pos_b[2]);
|
|
245
|
+
if (cost < best) {
|
|
246
|
+
best = cost;
|
|
247
|
+
best_survivor = v2;
|
|
248
|
+
best_victim = v1;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
// Interior edge.
|
|
254
|
+
const b1 = _vboundary[v1] === 1;
|
|
255
|
+
const b2 = _vboundary[v2] === 1;
|
|
256
|
+
|
|
257
|
+
if (b1 && b2) {
|
|
258
|
+
// both on the boundary, edge interior -> pinching / loop-welding, forbidden
|
|
259
|
+
return Number.POSITIVE_INFINITY;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!b2) {
|
|
263
|
+
// remove v2, keep v1
|
|
264
|
+
const cost = quadric_cost_at(q1, q2, _pos_a[0], _pos_a[1], _pos_a[2]);
|
|
265
|
+
if (cost < best) {
|
|
266
|
+
best = cost;
|
|
267
|
+
best_survivor = v1;
|
|
268
|
+
best_victim = v2;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!b1) {
|
|
273
|
+
// remove v1, keep v2
|
|
274
|
+
const cost = quadric_cost_at(q1, q2, _pos_b[0], _pos_b[1], _pos_b[2]);
|
|
275
|
+
if (cost < best) {
|
|
276
|
+
best = cost;
|
|
277
|
+
best_survivor = v2;
|
|
278
|
+
best_victim = v1;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
_choice_survivor = best_survivor;
|
|
284
|
+
_choice_victim = best_victim;
|
|
285
|
+
|
|
286
|
+
return best;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Would removing `victim` (folding it onto the stationary `survivor`) invert or degenerate
|
|
291
|
+
* any of the faces it reshapes? Checks every face incident to `victim` that does not also
|
|
292
|
+
* touch `survivor` (those are the collapse faces, which die) by comparing each triangle's
|
|
293
|
+
* normal before and after the substitution. A non-positive dot means a flip or a collapse to
|
|
294
|
+
* zero area -- both reject the collapse.
|
|
295
|
+
*
|
|
296
|
+
* This guards the in-plane fold that the quadric error cannot see: on a flat region every
|
|
297
|
+
* candidate scores ~0, but a non-star-shaped one-ring would still overlap if collapsed.
|
|
298
|
+
*
|
|
299
|
+
* @param {BinaryTopology} mesh
|
|
300
|
+
* @param {number} victim
|
|
301
|
+
* @param {number} survivor
|
|
302
|
+
* @returns {boolean}
|
|
303
|
+
*/
|
|
304
|
+
function collapse_would_fold(mesh, victim, survivor) {
|
|
305
|
+
mesh.vertex_read_coordinate(_pos_s, 0, survivor);
|
|
306
|
+
mesh.vertex_read_coordinate(_pos_v, 0, victim);
|
|
307
|
+
|
|
308
|
+
const face_pool = mesh.faces;
|
|
309
|
+
|
|
310
|
+
let e = mesh.vertex_read_edge(victim);
|
|
311
|
+
|
|
312
|
+
if (e === NULL_POINTER) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const start_e = e;
|
|
317
|
+
|
|
318
|
+
do {
|
|
319
|
+
const l0 = mesh.edge_read_loop(e);
|
|
320
|
+
|
|
321
|
+
if (l0 !== NULL_POINTER) {
|
|
322
|
+
let l = l0;
|
|
323
|
+
|
|
324
|
+
do {
|
|
325
|
+
// each face incident to `victim` is reached exactly once via its victim-anchored loop
|
|
326
|
+
if (mesh.loop_read_vertex(l) === victim) {
|
|
327
|
+
const f = mesh.loop_read_face(l);
|
|
328
|
+
|
|
329
|
+
if (f !== NULL_POINTER && face_pool.is_allocated(f)) {
|
|
330
|
+
const lb = mesh.loop_read_next(l);
|
|
331
|
+
const lc = mesh.loop_read_next(lb);
|
|
332
|
+
|
|
333
|
+
// triangle faces only
|
|
334
|
+
if (mesh.loop_read_next(lc) === l) {
|
|
335
|
+
const vb = mesh.loop_read_vertex(lb);
|
|
336
|
+
const vc = mesh.loop_read_vertex(lc);
|
|
337
|
+
|
|
338
|
+
// skip the faces that die in the collapse (they touch the survivor)
|
|
339
|
+
if (vb !== survivor && vc !== survivor) {
|
|
340
|
+
mesh.vertex_read_coordinate(_pos_b, 0, vb);
|
|
341
|
+
mesh.vertex_read_coordinate(_pos_c, 0, vc);
|
|
342
|
+
|
|
343
|
+
triangle_cross(
|
|
344
|
+
_n_before,
|
|
345
|
+
_pos_v[0], _pos_v[1], _pos_v[2],
|
|
346
|
+
_pos_b[0], _pos_b[1], _pos_b[2],
|
|
347
|
+
_pos_c[0], _pos_c[1], _pos_c[2]
|
|
348
|
+
);
|
|
349
|
+
triangle_cross(
|
|
350
|
+
_n_after,
|
|
351
|
+
_pos_s[0], _pos_s[1], _pos_s[2],
|
|
352
|
+
_pos_b[0], _pos_b[1], _pos_b[2],
|
|
353
|
+
_pos_c[0], _pos_c[1], _pos_c[2]
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
const dot = _n_before[0] * _n_after[0]
|
|
357
|
+
+ _n_before[1] * _n_after[1]
|
|
358
|
+
+ _n_before[2] * _n_after[2];
|
|
359
|
+
|
|
360
|
+
if (dot <= 0) {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
l = mesh.loop_read_radial_next(l);
|
|
369
|
+
} while (l !== l0 && l !== NULL_POINTER);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
e = (mesh.edge_read_vertex1(e) === victim)
|
|
373
|
+
? mesh.edge_read_v1_disk_next(e)
|
|
374
|
+
: mesh.edge_read_v2_disk_next(e);
|
|
375
|
+
} while (e !== start_e && e !== NULL_POINTER);
|
|
376
|
+
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Re-cost the collapses affected by a collapse that produced survivor `s`. Re-evaluates `s`'s
|
|
382
|
+
* incident edges (their endpoints' valence, quadric, and -- for the boundary ones -- the outline
|
|
383
|
+
* shape all moved) and updates the heap in place: still-collapsible edges are inserted/updated,
|
|
384
|
+
* edges that left the tolerance are removed. Edges further out keep their old heap score; this only
|
|
385
|
+
* affects pop *order*, never correctness, because every collapse is re-validated against the live
|
|
386
|
+
* mesh when it is finally popped.
|
|
387
|
+
*
|
|
388
|
+
* @param {BinaryTopology} mesh
|
|
389
|
+
* @param {number} s
|
|
390
|
+
* @param {Quadric3[]} quadratics
|
|
391
|
+
* @param {number} eps
|
|
392
|
+
* @param {number} eps_sq
|
|
393
|
+
* @param {FibonacciHeap} heap
|
|
394
|
+
*/
|
|
395
|
+
function requeue_ring(mesh, s, quadratics, eps, eps_sq, heap) {
|
|
396
|
+
// The survivor and its neighbours are exactly the vertices whose degree/boundary status a
|
|
397
|
+
// collapse can change, so refresh their cache here (before choose_collapse reads it) rather
|
|
398
|
+
// than walking the disk inside every choose_collapse.
|
|
399
|
+
recompute_vertex_cache(mesh, s);
|
|
400
|
+
|
|
401
|
+
let e = mesh.vertex_read_edge(s);
|
|
402
|
+
|
|
403
|
+
if (e === NULL_POINTER) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const start_e = e;
|
|
408
|
+
|
|
409
|
+
do {
|
|
410
|
+
const v1 = mesh.edge_read_vertex1(e);
|
|
411
|
+
const w = (v1 === s) ? mesh.edge_read_vertex2(e) : v1;
|
|
412
|
+
|
|
413
|
+
recompute_vertex_cache(mesh, w);
|
|
414
|
+
|
|
415
|
+
const cost = choose_collapse(mesh, e, quadratics, eps);
|
|
416
|
+
|
|
417
|
+
if (cost <= eps_sq) {
|
|
418
|
+
heap.insert_or_update(e, cost);
|
|
419
|
+
} else {
|
|
420
|
+
heap.remove(e);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
e = (v1 === s)
|
|
424
|
+
? mesh.edge_read_v1_disk_next(e)
|
|
425
|
+
: mesh.edge_read_v2_disk_next(e);
|
|
426
|
+
} while (e !== start_e && e !== NULL_POINTER);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Clean up a triangle mesh in place by removing only geometrically redundant vertices, so the
|
|
431
|
+
* surface and its boundary are preserved to within `max_error`. Unlike {@link bt_mesh_simplify}
|
|
432
|
+
* (quadric LoD decimation driven to a face-count target, free to relocate vertices), this is a
|
|
433
|
+
* conservative half-edge cleanup: it never moves a surviving vertex and never changes the
|
|
434
|
+
* boundary outline or hole topology by more than `max_error`. It removes the dense interior of
|
|
435
|
+
* flat/near-coplanar regions and collinear runs of boundary vertices, and stops once the
|
|
436
|
+
* cheapest remaining collapse would exceed the tolerance.
|
|
437
|
+
*
|
|
438
|
+
* Intended for navmesh post-processing, where the input is a walkable surface whose outline and
|
|
439
|
+
* holes (obstacle footprints) carry meaning and must not drift, but whose interior inherits far
|
|
440
|
+
* more triangles than navigation needs.
|
|
441
|
+
*
|
|
442
|
+
* @param {BinaryTopology} mesh Mesh to clean up in-place.
|
|
443
|
+
* @param {number} max_error Maximum world-space deviation allowed for any removed vertex and for
|
|
444
|
+
* the boundary polyline. A no-op when <= 0.
|
|
445
|
+
*/
|
|
446
|
+
export function bt_mesh_simplify_by_error(mesh, max_error) {
|
|
447
|
+
assert.defined(mesh, 'mesh');
|
|
448
|
+
assert.notNull(mesh, 'mesh');
|
|
449
|
+
assert.equal(mesh.isBinaryTopology, true, 'mesh.isBinaryTopology !== true');
|
|
450
|
+
assert.isNumber(max_error, 'max_error');
|
|
451
|
+
|
|
452
|
+
if (!(max_error > 0)) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const eps = max_error;
|
|
457
|
+
const eps_sq = eps * eps;
|
|
458
|
+
|
|
459
|
+
const edge_pool = mesh.edges;
|
|
460
|
+
const edge_count = edge_pool.size;
|
|
461
|
+
|
|
462
|
+
if (edge_count === 0) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const quadratics = bt_mesh_compute_vertex_quadratics(mesh);
|
|
467
|
+
|
|
468
|
+
// O(1) degree/boundary caches for choose_collapse, seeded once and kept current per collapse.
|
|
469
|
+
const vertex_count = mesh.vertices.size;
|
|
470
|
+
_degree = new Int32Array(vertex_count);
|
|
471
|
+
_vboundary = new Uint8Array(vertex_count);
|
|
472
|
+
for (let v = 0; v < vertex_count; v++) {
|
|
473
|
+
if (mesh.vertices.is_allocated(v)) {
|
|
474
|
+
recompute_vertex_cache(mesh, v);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Indexed min priority queue keyed by edge id: re-costs are in-place insert_or_update / remove
|
|
479
|
+
// (O(1) expected via its internal id->handle map), so there are no duplicate or stale entries to
|
|
480
|
+
// track -- the queue always holds exactly the currently-collapsible edges. Edges are only ever
|
|
481
|
+
// killed, never created, so ids stay < edge_count for the whole run.
|
|
482
|
+
const heap = new FibonacciHeap(edge_count);
|
|
483
|
+
|
|
484
|
+
for (let e = 0; e < edge_count; e++) {
|
|
485
|
+
if (!edge_pool.is_allocated(e)) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const cost = choose_collapse(mesh, e, quadratics, eps);
|
|
490
|
+
|
|
491
|
+
if (cost <= eps_sq) {
|
|
492
|
+
heap.insert(e, cost);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Safety bound against an unforeseen non-terminating re-queue cycle. Every accepted collapse
|
|
497
|
+
// removes >= 1 face; collapsed-edge and over-tolerance/fold pops also burn iterations.
|
|
498
|
+
let guard = (edge_count + heap.size) * 8 + 16;
|
|
499
|
+
|
|
500
|
+
while (!heap.is_empty()) {
|
|
501
|
+
if (--guard < 0) {
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const e = heap.pop_min();
|
|
506
|
+
|
|
507
|
+
if (!edge_pool.is_allocated(e)) {
|
|
508
|
+
// edge already collapsed away (its queue entry was never removed; drop it now)
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Re-validate against the live mesh: a 2-ring change may have raised this edge's cost
|
|
513
|
+
// without re-queueing it (requeue only touches the 1-ring). This also fills in the chosen
|
|
514
|
+
// collapse direction (_choice_survivor / _choice_victim).
|
|
515
|
+
const cost = choose_collapse(mesh, e, quadratics, eps);
|
|
516
|
+
|
|
517
|
+
if (cost > eps_sq) {
|
|
518
|
+
// no longer within tolerance; a neighbouring collapse may re-queue it later
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const survivor = _choice_survivor;
|
|
523
|
+
const victim = _choice_victim;
|
|
524
|
+
|
|
525
|
+
if (survivor === NULL_POINTER || victim === NULL_POINTER) {
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (collapse_would_fold(mesh, victim, survivor)) {
|
|
530
|
+
// a neighbouring collapse may re-queue it later
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// bt_edge_collapse keeps v1; make the survivor v1 so it is the one that stays.
|
|
535
|
+
if (mesh.edge_read_vertex1(e) !== survivor) {
|
|
536
|
+
bt_edge_swap_vertex_slots(mesh, e);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// The survivor keeps its exact coordinate -- nothing in the mesh moves.
|
|
540
|
+
mesh.vertex_read_coordinate(_pos_target, 0, survivor);
|
|
541
|
+
|
|
542
|
+
// Fold the victim's accumulated planes into the survivor before the topology changes,
|
|
543
|
+
// so the survivor's quadric keeps measuring deviation from the original surface.
|
|
544
|
+
quadratics[survivor].add(quadratics[victim]);
|
|
545
|
+
quadratics[victim] = null;
|
|
546
|
+
|
|
547
|
+
bt_edge_kill_parallels(mesh, e);
|
|
548
|
+
const s = bt_edge_collapse(mesh, e, _pos_target, 0);
|
|
549
|
+
bt_vert_fuse_duplicate_edges(mesh, s);
|
|
550
|
+
|
|
551
|
+
requeue_ring(mesh, s, quadratics, eps, eps_sq, heap);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
bt_mesh_cleanup_faceless_references(mesh);
|
|
555
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swap the v1/v2 slots of an edge. Disk cycle pointers for the two slots are swapped
|
|
3
|
+
* alongside so that the disk cycles on both attached vertices remain valid.
|
|
4
|
+
*
|
|
5
|
+
* Useful before a half-edge collapse (e.g. {@link bt_edge_collapse}, which keeps v1 as
|
|
6
|
+
* the survivor) when the caller needs a specific endpoint to survive: swap first so the
|
|
7
|
+
* desired survivor sits in the v1 slot.
|
|
8
|
+
*
|
|
9
|
+
* @param {BinaryTopology} mesh
|
|
10
|
+
* @param {number} edge_id
|
|
11
|
+
*/
|
|
12
|
+
export function bt_edge_swap_vertex_slots(mesh: BinaryTopology, edge_id: number): void;
|
|
13
|
+
//# sourceMappingURL=bt_edge_swap_vertex_slots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bt_edge_swap_vertex_slots.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_swap_vertex_slots.js"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,yEAFW,MAAM,QAkBhB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swap the v1/v2 slots of an edge. Disk cycle pointers for the two slots are swapped
|
|
3
|
+
* alongside so that the disk cycles on both attached vertices remain valid.
|
|
4
|
+
*
|
|
5
|
+
* Useful before a half-edge collapse (e.g. {@link bt_edge_collapse}, which keeps v1 as
|
|
6
|
+
* the survivor) when the caller needs a specific endpoint to survive: swap first so the
|
|
7
|
+
* desired survivor sits in the v1 slot.
|
|
8
|
+
*
|
|
9
|
+
* @param {BinaryTopology} mesh
|
|
10
|
+
* @param {number} edge_id
|
|
11
|
+
*/
|
|
12
|
+
export function bt_edge_swap_vertex_slots(mesh, edge_id) {
|
|
13
|
+
const v1 = mesh.edge_read_vertex1(edge_id);
|
|
14
|
+
const v2 = mesh.edge_read_vertex2(edge_id);
|
|
15
|
+
|
|
16
|
+
const v1_next = mesh.edge_read_v1_disk_next(edge_id);
|
|
17
|
+
const v1_prev = mesh.edge_read_v1_disk_prev(edge_id);
|
|
18
|
+
const v2_next = mesh.edge_read_v2_disk_next(edge_id);
|
|
19
|
+
const v2_prev = mesh.edge_read_v2_disk_prev(edge_id);
|
|
20
|
+
|
|
21
|
+
mesh.edge_write_vertex1(edge_id, v2);
|
|
22
|
+
mesh.edge_write_vertex2(edge_id, v1);
|
|
23
|
+
|
|
24
|
+
mesh.edge_write_v1_disk_next(edge_id, v2_next);
|
|
25
|
+
mesh.edge_write_v1_disk_prev(edge_id, v2_prev);
|
|
26
|
+
mesh.edge_write_v2_disk_next(edge_id, v1_next);
|
|
27
|
+
mesh.edge_write_v2_disk_prev(edge_id, v1_prev);
|
|
28
|
+
}
|
|
@@ -68,5 +68,5 @@ export class BisectionScratch {
|
|
|
68
68
|
*/
|
|
69
69
|
release_vertex_partition_slot(): void;
|
|
70
70
|
}
|
|
71
|
-
import { IndexedFloatMaxHeap } from "
|
|
71
|
+
import { IndexedFloatMaxHeap } from "../../../../collection/heap/IndexedFloatMaxHeap.js";
|
|
72
72
|
//# sourceMappingURL=BisectionScratch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BisectionScratch.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/graph/metis/native/bisection/BisectionScratch.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH;IACI;;OAEG;IACH,8BAFW,MAAM,EA8BhB;IA3BG,yBAAwC;IAIxC,+BAA0D;IAC1D,wCAAmE;IACnE,6BAAwD;IACxD,6BAAwD;IACxD,+BAA2C;IAC3C,qCAAgE;IAIhE,gCAGC;IACD,gCAA2D;IAE3D,6BAAwD;IACxD,gCAA2D;IAI3D,4BAA4B;IAC5B,uBADW,WAAW,EAAE,CACO;IAC/B,kCAAkC;IAGtC;;;;;;;;;;OAUG;IACH,yCAHW,MAAM,GACJ,WAAW,CAavB;IAED;;;OAGG;IACH,sCAEC;CACJ;oCApGmC,
|
|
1
|
+
{"version":3,"file":"BisectionScratch.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/graph/metis/native/bisection/BisectionScratch.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH;IACI;;OAEG;IACH,8BAFW,MAAM,EA8BhB;IA3BG,yBAAwC;IAIxC,+BAA0D;IAC1D,wCAAmE;IACnE,6BAAwD;IACxD,6BAAwD;IACxD,+BAA2C;IAC3C,qCAAgE;IAIhE,gCAGC;IACD,gCAA2D;IAE3D,6BAAwD;IACxD,gCAA2D;IAI3D,4BAA4B;IAC5B,uBADW,WAAW,EAAE,CACO;IAC/B,kCAAkC;IAGtC;;;;;;;;;;OAUG;IACH,yCAHW,MAAM,GACJ,WAAW,CAavB;IAED;;;OAGG;IACH,sCAEC;CACJ;oCApGmC,oDAAoD"}
|
|
@@ -41,5 +41,5 @@ export class RefinementScratch {
|
|
|
41
41
|
partition_weight_min: Uint32Array;
|
|
42
42
|
partition_weight_max: Uint32Array;
|
|
43
43
|
}
|
|
44
|
-
import { IndexedFloatMaxHeap } from "
|
|
44
|
+
import { IndexedFloatMaxHeap } from "../../../../collection/heap/IndexedFloatMaxHeap.js";
|
|
45
45
|
//# sourceMappingURL=RefinementScratch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RefinementScratch.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/graph/metis/native/refine/RefinementScratch.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH;IACI;;;OAGG;IACH,8BAHW,MAAM,mBACN,MAAM,EAkBhB;IAfG,yBAAwC;IACxC,wBAAsC;IAEtC,gCAGC;IAED,yBAAoD;IACpD,8BAAyD;IACzD,uCAAkE;IAGlE,kCAA4D;IAC5D,kCAA4D;CAEnE;oCApDmC,
|
|
1
|
+
{"version":3,"file":"RefinementScratch.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/graph/metis/native/refine/RefinementScratch.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH;IACI;;;OAGG;IACH,8BAHW,MAAM,mBACN,MAAM,EAkBhB;IAfG,yBAAwC;IACxC,wBAAsC;IAEtC,gCAGC;IAED,yBAAoD;IACpD,8BAAyD;IACzD,uCAAkE;IAGlE,kCAA4D;IAC5D,kCAA4D;CAEnE;oCApDmC,oDAAoD"}
|
|
@@ -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":"AAuDA;;;;;;;;;;GAUG;AACH,wKATW,cAAc,QAmNxB;+BA3Q8B,mEAAmE"}
|
|
@@ -41,8 +41,17 @@ import {
|
|
|
41
41
|
import {
|
|
42
42
|
bt_mesh_fill_small_holes
|
|
43
43
|
} from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js";
|
|
44
|
+
import {
|
|
45
|
+
bt_mesh_simplify_by_error
|
|
46
|
+
} from "../../../../core/geom/3d/topology/struct/binary/io/bt_mesh_simplify_by_error.js";
|
|
44
47
|
import { clip_soup_against_overhangs } from "./clip_soup_against_overhangs.js";
|
|
45
48
|
|
|
49
|
+
// Fraction of the agent radius used as the navmesh cleanup error budget (see the cleanup pass in
|
|
50
|
+
// navmesh_build_topology). A larger agent cannot resolve fine surface detail and the walkable
|
|
51
|
+
// surface was already inset by agent_radius during erosion, so a small fraction of it is a safe,
|
|
52
|
+
// navigation-imperceptible bound on how far cleanup may move the surface or the walkable outline.
|
|
53
|
+
const NAVMESH_SIMPLIFY_ERROR_FRACTION = 0.05;
|
|
54
|
+
|
|
46
55
|
|
|
47
56
|
/**
|
|
48
57
|
* Build from given scene geometry
|
|
@@ -98,6 +107,11 @@ export function navmesh_build_topology({
|
|
|
98
107
|
let raw_triangles = [];
|
|
99
108
|
let triangle_count = 0;
|
|
100
109
|
|
|
110
|
+
// NOTE: the inherited source tessellation is cleaned up at the end of this function by
|
|
111
|
+
// bt_mesh_simplify_by_error (interior coplanar fans + collinear boundary runs collapsed). A
|
|
112
|
+
// further win -- re-triangulating large flat regions to a minimal polygon set -- is left as a
|
|
113
|
+
// possible follow-up.
|
|
114
|
+
|
|
101
115
|
for (let face_id = 0; face_id < source_face_count; face_id++) {
|
|
102
116
|
|
|
103
117
|
source.face_read_normal(scratch_normal, 0, face_id);
|
|
@@ -234,6 +248,17 @@ export function navmesh_build_topology({
|
|
|
234
248
|
}
|
|
235
249
|
}
|
|
236
250
|
|
|
251
|
+
// --- Clean up the dense triangulation inherited from the source render geometry ---
|
|
252
|
+
// The navmesh arrives with far more triangles than navigation needs (a flat floor comes in
|
|
253
|
+
// as many coplanar triangles). Collapse only the geometrically redundant ones -- interior
|
|
254
|
+
// near-coplanar fans and collinear runs of boundary vertices -- without moving the surface
|
|
255
|
+
// or the walkable outline (and obstacle holes) by more than a small fraction of the agent
|
|
256
|
+
// radius. With agent_radius 0 the tolerance is 0 and this is a no-op.
|
|
257
|
+
if (agent_radius > 0) {
|
|
258
|
+
bt_mesh_simplify_by_error(mesh, agent_radius * NAVMESH_SIMPLIFY_ERROR_FRACTION);
|
|
259
|
+
bt_mesh_compact(mesh);
|
|
260
|
+
}
|
|
261
|
+
|
|
237
262
|
// face normals are consumed by navigation queries (string-pulling portal normals), populate them now
|
|
238
263
|
bt_mesh_compute_face_normals(mesh);
|
|
239
264
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"IndexedFloatMaxHeap.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/graph/metis/native/refine/IndexedFloatMaxHeap.js"],"names":[],"mappings":"AA6BA;IACI;;;OAGG;IACH,yBAHW,MAAM,0BACN,MAAM,EAsBhB;IAbG,wBAA4C;IAC5C,eAAe;IAEf,2BAA4E;IAC5E,2BAAwD;IACxD,6BAA0D;IAE1D;;;OAGG;IACH,cAFU,WAAW,CAE2B;IAIpD,mBAEC;IAED;;;OAGG;IACH,cAQC;IAED,2BAEC;IAED;;;OAGG;IACH,qBAkBC;IAED;;;;OAIG;IACH,oBAsBC;IAED;;;;OAIG;IACH,kBAYC;IAED,wBAUC;IAED;;;OAGG;IACH,WAHW,MAAM,SACN,MAAM,QAehB;IAED;;OAEG;IACH,WAFa,MAAM,CAuBlB;IAED;;;OAGG;IACH,WAHW,MAAM,aACN,MAAM,QAehB;IAED;;;OAGG;IACH,WAHW,MAAM,GACJ,OAAO,CA+BnB;CACJ"}
|
|
File without changes
|