@woosh/meep-engine 2.163.1 → 2.163.3
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/equirectangular/equirectangular_direction_to_uv.d.ts +12 -0
- package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.d.ts.map +1 -0
- package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.js +18 -0
- package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.d.ts +14 -0
- package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.d.ts.map +1 -0
- package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.js +24 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +368 -290
- package/src/core/geom/vec3/v3_uniform_sample_cone.d.ts +11 -0
- package/src/core/geom/vec3/v3_uniform_sample_cone.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_uniform_sample_cone.js +21 -0
- package/src/core/math/physics/brdf/cone_cosine_from_roughness.d.ts +13 -0
- package/src/core/math/physics/brdf/cone_cosine_from_roughness.d.ts.map +1 -0
- package/src/core/math/physics/brdf/cone_cosine_from_roughness.js +28 -0
- package/src/core/math/physics/brdf/reflection_sample_weight.d.ts +18 -0
- package/src/core/math/physics/brdf/reflection_sample_weight.d.ts.map +1 -0
- package/src/core/math/physics/brdf/reflection_sample_weight.js +48 -0
- package/src/engine/graphics/GraphicsEngine.d.ts.map +1 -1
- package/src/engine/graphics/GraphicsEngine.js +52 -0
- package/src/engine/graphics/ecs/path/tube/build/build_geometry_catmullrom.d.ts.map +1 -1
- package/src/engine/graphics/ecs/path/tube/build/build_geometry_catmullrom.js +306 -226
- package/src/engine/graphics/ecs/path/tube/build/make_cap.d.ts.map +1 -1
- package/src/engine/graphics/ecs/path/tube/build/make_cap.js +26 -17
- package/src/engine/graphics/sh3/sky/hosek/make_environment_sky_hosek.d.ts +26 -0
- package/src/engine/graphics/sh3/sky/hosek/make_environment_sky_hosek.d.ts.map +1 -0
- package/src/engine/graphics/sh3/sky/hosek/make_environment_sky_hosek.js +49 -0
- package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.d.ts +26 -0
- package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.d.ts.map +1 -0
- package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.js +70 -0
- package/src/engine/graphics/sh3/sky/hosek/setup_environment_sky_from_ecd.d.ts +24 -0
- package/src/engine/graphics/sh3/sky/hosek/setup_environment_sky_from_ecd.d.ts.map +1 -0
- package/src/engine/graphics/sh3/sky/hosek/setup_environment_sky_from_ecd.js +51 -0
- package/src/engine/graphics/texture/EnvironmentTextureProjection.d.ts +9 -0
- package/src/engine/graphics/texture/EnvironmentTextureProjection.d.ts.map +1 -0
- package/src/engine/graphics/texture/EnvironmentTextureProjection.js +15 -0
- package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.d.ts +44 -0
- package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.d.ts.map +1 -0
- package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.js +189 -0
- package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.d.ts +25 -0
- package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.d.ts.map +1 -0
- package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.js +51 -0
- package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.d.ts +15 -0
- package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.d.ts.map +1 -0
- package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.js +63 -0
- package/src/engine/navigation/mesh/build/bt_mesh_carve_height_clearance.d.ts +27 -0
- package/src/engine/navigation/mesh/build/bt_mesh_carve_height_clearance.d.ts.map +1 -0
- package/src/engine/navigation/mesh/build/bt_mesh_carve_height_clearance.js +323 -0
- package/src/engine/navigation/mesh/build/navmesh_build_topology.d.ts.map +1 -1
- package/src/engine/navigation/mesh/build/navmesh_build_topology.js +223 -226
- package/src/engine/.fuse_hidden0000001500000001 +0 -581
|
@@ -1,290 +1,368 @@
|
|
|
1
|
-
import { assert } from "../../../../../../assert.js";
|
|
2
|
-
import { clamp01 } from "../../../../../../math/clamp01.js";
|
|
3
|
-
import { inverseLerp } from "../../../../../../math/inverseLerp.js";
|
|
4
|
-
import {
|
|
5
|
-
line3_compute_segment_point_distance_eikonal
|
|
6
|
-
} from "../../../../line/line3_compute_segment_point_distance_eikonal.js";
|
|
7
|
-
import { NULL_POINTER } from "../BinaryTopology.js";
|
|
8
|
-
import { bt_face_get_incenter } from "../query/bt_face_get_incenter.js";
|
|
9
|
-
import { bt_mesh_build_boundary_distance_field } from "../query/bt_mesh_build_boundary_distance_field.js";
|
|
10
|
-
import { bt_mesh_cleanup_faceless_references } from "./bt_mesh_cleanup_faceless_references.js";
|
|
11
|
-
import { bt_edge_split } from "./edge/bt_edge_split.js";
|
|
12
|
-
import { bt_face_kill } from "./face/bt_face_kill.js";
|
|
13
|
-
import { bt_mesh_face_poke } from "./face/bt_face_poke.js";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* @param {
|
|
26
|
-
* @param {number}
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
1
|
+
import { assert } from "../../../../../../assert.js";
|
|
2
|
+
import { clamp01 } from "../../../../../../math/clamp01.js";
|
|
3
|
+
import { inverseLerp } from "../../../../../../math/inverseLerp.js";
|
|
4
|
+
import {
|
|
5
|
+
line3_compute_segment_point_distance_eikonal
|
|
6
|
+
} from "../../../../line/line3_compute_segment_point_distance_eikonal.js";
|
|
7
|
+
import { NULL_POINTER } from "../BinaryTopology.js";
|
|
8
|
+
import { bt_face_get_incenter } from "../query/bt_face_get_incenter.js";
|
|
9
|
+
import { bt_mesh_build_boundary_distance_field } from "../query/bt_mesh_build_boundary_distance_field.js";
|
|
10
|
+
import { bt_mesh_cleanup_faceless_references } from "./bt_mesh_cleanup_faceless_references.js";
|
|
11
|
+
import { bt_edge_split } from "./edge/bt_edge_split.js";
|
|
12
|
+
import { bt_face_kill } from "./face/bt_face_kill.js";
|
|
13
|
+
import { bt_mesh_face_poke } from "./face/bt_face_poke.js";
|
|
14
|
+
import { bt_face_get_neighbour_faces } from "../query/bt_face_get_neighbour_faces.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Shrinks an island of faces by a given distance.
|
|
18
|
+
* Conceptually, this moves the outline of the island inwards.
|
|
19
|
+
* Assumes the input mesh is triangulated, see {@link bt_mesh_triangulate}.
|
|
20
|
+
*
|
|
21
|
+
* It is assumed that the island does not share any edges/vertices with any other part of the mesh. If this assumption is violated - expect undefined behavior.
|
|
22
|
+
* An island is not restricted to all faces being co-planar, it may curve.
|
|
23
|
+
* The erosion distance may be smaller than the smallest triangle height in the island, or it may be significantly larger - there is no restriction.
|
|
24
|
+
*
|
|
25
|
+
* @param {BinaryTopology} mesh
|
|
26
|
+
* @param {number[]} faces
|
|
27
|
+
* @param {number} erode_distance
|
|
28
|
+
*/
|
|
29
|
+
export function bt_mesh_face_island_erode(
|
|
30
|
+
mesh,
|
|
31
|
+
faces,
|
|
32
|
+
erode_distance
|
|
33
|
+
) {
|
|
34
|
+
|
|
35
|
+
if (faces.length === 0 || erode_distance <= 0) {
|
|
36
|
+
// no work to do
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 1. + 2. Map the island's vertices/edges and detect boundary (single radial loop) edges/vertices.
|
|
41
|
+
// Extracted into a helper so it can be recomputed after the connectivity pre-pass mutates the mesh.
|
|
42
|
+
function collect_island_sets(island_faces) {
|
|
43
|
+
const iv = new Set();
|
|
44
|
+
const ie = new Set();
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < island_faces.length; i++) {
|
|
47
|
+
const f = island_faces[i];
|
|
48
|
+
const l_first = mesh.face_read_loop(f);
|
|
49
|
+
if (l_first === NULL_POINTER) continue;
|
|
50
|
+
|
|
51
|
+
let l_curr = l_first;
|
|
52
|
+
do {
|
|
53
|
+
iv.add(mesh.loop_read_vertex(l_curr));
|
|
54
|
+
ie.add(mesh.loop_read_edge(l_curr));
|
|
55
|
+
l_curr = mesh.loop_read_next(l_curr);
|
|
56
|
+
} while (l_curr !== l_first);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const bv = new Set();
|
|
60
|
+
const be = new Set();
|
|
61
|
+
|
|
62
|
+
for (const e of ie) {
|
|
63
|
+
const l = mesh.edge_read_loop(e);
|
|
64
|
+
if (mesh.loop_read_radial_next(l) === l) {
|
|
65
|
+
bv.add(mesh.edge_read_vertex1(e));
|
|
66
|
+
bv.add(mesh.edge_read_vertex2(e));
|
|
67
|
+
be.add(e);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { island_vertices: iv, island_edges: ie, boundary_vertices: bv, boundary_edges_set: be };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Re-derive the (now larger) face list of this island after edge splits by flood-filling
|
|
75
|
+
// edge-adjacency from the original faces. bt_edge_split reuses the original face ids for one half
|
|
76
|
+
// of each split, so they remain valid, allocated seeds.
|
|
77
|
+
function recompute_island_faces(seed_faces) {
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
const stack = [];
|
|
80
|
+
const result = [];
|
|
81
|
+
const nbr = [];
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < seed_faces.length; i++) {
|
|
84
|
+
const f = seed_faces[i];
|
|
85
|
+
if (mesh.faces.is_allocated(f)) {
|
|
86
|
+
stack.push(f);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
while (stack.length > 0) {
|
|
91
|
+
const f = stack.pop();
|
|
92
|
+
if (seen.has(f)) continue;
|
|
93
|
+
seen.add(f);
|
|
94
|
+
result.push(f);
|
|
95
|
+
|
|
96
|
+
const n = bt_face_get_neighbour_faces(nbr, 0, mesh, f);
|
|
97
|
+
for (let i = 0; i < n; i++) {
|
|
98
|
+
const g = nbr[i];
|
|
99
|
+
if (!seen.has(g)) {
|
|
100
|
+
stack.push(g);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let island_faces = faces;
|
|
109
|
+
|
|
110
|
+
let { island_vertices, island_edges, boundary_vertices, boundary_edges_set } = collect_island_sets(island_faces);
|
|
111
|
+
|
|
112
|
+
if (boundary_vertices.size === 0) {
|
|
113
|
+
// no boundary vertices found
|
|
114
|
+
// should never happen if the mesh is valid
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------
|
|
119
|
+
// 2.5. CONNECTIVITY PRE-PASS (fix for erosion fragmentation)
|
|
120
|
+
// ---------------------------------------------------------
|
|
121
|
+
// The boundary distance field is sampled only at vertices and interpolated linearly along edges.
|
|
122
|
+
// An *interior* edge whose BOTH endpoints lie on the boundary therefore reads as ~0 along its
|
|
123
|
+
// entire length, even where its middle sits deep inside the island (the shared diagonal of a coarse
|
|
124
|
+
// quad, or the seam between two large coplanar tiles). Erosion then culls a band straddling that
|
|
125
|
+
// edge and severs one connected island into disconnected pieces.
|
|
126
|
+
//
|
|
127
|
+
// Fix: insert an interior vertex at the midpoint of every such edge. The new vertex is genuinely
|
|
128
|
+
// interior, so the eikonal field gives it the correct (large) clearance and the contour keeps the
|
|
129
|
+
// deep interior alive, preserving connectivity. A single pass suffices: splitting a
|
|
130
|
+
// boundary-boundary interior edge yields only boundary-interior edges, creating no new offenders.
|
|
131
|
+
{
|
|
132
|
+
const split_targets = [];
|
|
133
|
+
|
|
134
|
+
for (const e of island_edges) {
|
|
135
|
+
if (boundary_edges_set.has(e)) {
|
|
136
|
+
// a true boundary edge defines the outline - never split it
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const v1 = mesh.edge_read_vertex1(e);
|
|
141
|
+
const v2 = mesh.edge_read_vertex2(e);
|
|
142
|
+
|
|
143
|
+
if (boundary_vertices.has(v1) && boundary_vertices.has(v2)) {
|
|
144
|
+
split_targets.push(e);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (split_targets.length > 0) {
|
|
149
|
+
for (let i = 0; i < split_targets.length; i++) {
|
|
150
|
+
bt_edge_split(mesh, split_targets[i], 0.5);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
island_faces = recompute_island_faces(faces);
|
|
154
|
+
|
|
155
|
+
({ island_vertices, island_edges, boundary_vertices, boundary_edges_set } = collect_island_sets(island_faces));
|
|
156
|
+
|
|
157
|
+
if (boundary_vertices.size === 0) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 3. Compute Geodesic Distance Field
|
|
164
|
+
/**
|
|
165
|
+
* Vertex -> Distance from boundary
|
|
166
|
+
* @type {Map<number, number>}
|
|
167
|
+
*/
|
|
168
|
+
const vertex_boundary_distances = new Map();
|
|
169
|
+
bt_mesh_build_boundary_distance_field(vertex_boundary_distances, mesh, island_vertices, boundary_vertices);
|
|
170
|
+
// ---------------------------------------------------------
|
|
171
|
+
// 3.5. PEAK RESCUE PASS (The Fix)
|
|
172
|
+
// Handle cases where a face is "submerged" by vertex values
|
|
173
|
+
// but geometrically contains a safe zone (e.g. single triangle).
|
|
174
|
+
// ---------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
// We snapshot the faces list because we might modify topology (poke)
|
|
177
|
+
const faces_to_check = [...island_faces];
|
|
178
|
+
const centroid = [];
|
|
179
|
+
const scratch_v1 = [];
|
|
180
|
+
const scratch_v2 = [];
|
|
181
|
+
|
|
182
|
+
for (const f of faces_to_check) {
|
|
183
|
+
// Collect face vertices and check if they are all "dead"
|
|
184
|
+
let l_curr = mesh.face_read_loop(f);
|
|
185
|
+
const l_end = l_curr;
|
|
186
|
+
let all_dead = true;
|
|
187
|
+
|
|
188
|
+
do {
|
|
189
|
+
const v = mesh.loop_read_vertex(l_curr);
|
|
190
|
+
if ((vertex_boundary_distances.get(v) || 0) >= erode_distance) {
|
|
191
|
+
all_dead = false;
|
|
192
|
+
}
|
|
193
|
+
l_curr = mesh.loop_read_next(l_curr);
|
|
194
|
+
} while (l_curr !== l_end);
|
|
195
|
+
|
|
196
|
+
if (all_dead) {
|
|
197
|
+
// This face is slated for total deletion.
|
|
198
|
+
// Check if we should rescue it by adding a peak vertex.
|
|
199
|
+
|
|
200
|
+
// 1. Calculate Centroid (incenter only for triangle, falls back to centroid for other N-gons)
|
|
201
|
+
bt_face_get_incenter(centroid, 0, mesh, f);
|
|
202
|
+
|
|
203
|
+
// 2. Calculate Centroid Distance to Boundary
|
|
204
|
+
// (For a single island face, min dist to its own boundary edges)
|
|
205
|
+
let min_dist = Infinity;
|
|
206
|
+
// Iterate edges of this face
|
|
207
|
+
l_curr = mesh.face_read_loop(f);
|
|
208
|
+
do {
|
|
209
|
+
const e = mesh.loop_read_edge(l_curr);
|
|
210
|
+
|
|
211
|
+
const v1 = mesh.edge_read_vertex1(e);
|
|
212
|
+
const v2 = mesh.edge_read_vertex2(e);
|
|
213
|
+
|
|
214
|
+
if (island_vertices.has(v1) && island_vertices.has(v2)) {
|
|
215
|
+
|
|
216
|
+
mesh.vertex_read_coordinate(scratch_v1, 0, v1);
|
|
217
|
+
mesh.vertex_read_coordinate(scratch_v2, 0, v2);
|
|
218
|
+
|
|
219
|
+
const d1 = vertex_boundary_distances.get(v1);
|
|
220
|
+
const d2 = vertex_boundary_distances.get(v2);
|
|
221
|
+
|
|
222
|
+
if (d1 < erode_distance || d2 < erode_distance) {
|
|
223
|
+
// only consider edges that could cross the erosion threshold
|
|
224
|
+
|
|
225
|
+
const d = line3_compute_segment_point_distance_eikonal(
|
|
226
|
+
d1, d2,
|
|
227
|
+
scratch_v1[0], scratch_v1[1], scratch_v1[2],
|
|
228
|
+
scratch_v2[0], scratch_v2[1], scratch_v2[2],
|
|
229
|
+
centroid[0], centroid[1], centroid[2]
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (d < min_dist) {
|
|
233
|
+
min_dist = d;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
l_curr = mesh.loop_read_next(l_curr);
|
|
241
|
+
} while (l_curr !== l_end);
|
|
242
|
+
|
|
243
|
+
// If min_dist is Infinity, it means this face has NO boundary edges
|
|
244
|
+
// (it's internal). It should have inherited values from neighbors.
|
|
245
|
+
// We ignore it, or assume linear interpolation was correct.
|
|
246
|
+
// We only rescue faces touching the boundary that collapsed.
|
|
247
|
+
|
|
248
|
+
if (min_dist !== Infinity && min_dist > erode_distance) {
|
|
249
|
+
// RESCUE: The center is safe!
|
|
250
|
+
// Poke the face to create a vertex at the centroid.
|
|
251
|
+
const new_vert = bt_mesh_face_poke(mesh, f, centroid[0], centroid[1], centroid[2]);
|
|
252
|
+
|
|
253
|
+
// Update Data
|
|
254
|
+
island_vertices.add(new_vert);
|
|
255
|
+
vertex_boundary_distances.set(new_vert, min_dist);
|
|
256
|
+
|
|
257
|
+
// Read the new edges connected to the center and add to processing set
|
|
258
|
+
let e_first = mesh.vertex_read_edge(new_vert);
|
|
259
|
+
|
|
260
|
+
let e_scan = e_first;
|
|
261
|
+
do {
|
|
262
|
+
island_edges.add(e_scan);
|
|
263
|
+
|
|
264
|
+
const v1 = mesh.edge_read_vertex1(e_scan);
|
|
265
|
+
const is_v1 = v1 === new_vert;
|
|
266
|
+
|
|
267
|
+
// DEBUG checks
|
|
268
|
+
const v2 = mesh.edge_read_vertex2(e_scan);
|
|
269
|
+
if (is_v1) {
|
|
270
|
+
assert.equal(v1, new_vert, 'edge vertex does not match new vertex');
|
|
271
|
+
assert.notEqual(v2, new_vert, 'edge vertex does not match new vertex');
|
|
272
|
+
} else {
|
|
273
|
+
assert.equal(v2, new_vert, 'edge vertex does not match new vertex');
|
|
274
|
+
assert.notEqual(v1, new_vert, 'edge vertex does not match new vertex');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
e_scan = is_v1 ? mesh.edge_read_v1_disk_next(e_scan) : mesh.edge_read_v2_disk_next(e_scan);
|
|
278
|
+
} while (e_scan !== e_first);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// ---------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
// 4. Trace the Contour & Split Edges
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
*
|
|
288
|
+
* @type {{e: number, t: number}[]}
|
|
289
|
+
*/
|
|
290
|
+
const edges_to_split = [];
|
|
291
|
+
|
|
292
|
+
for (const e of island_edges) {
|
|
293
|
+
|
|
294
|
+
assert.equal(mesh.edges.is_allocated(e), true, 'edge is not allocated');
|
|
295
|
+
|
|
296
|
+
const v1 = mesh.edge_read_vertex1(e);
|
|
297
|
+
const v2 = mesh.edge_read_vertex2(e);
|
|
298
|
+
|
|
299
|
+
const d1 = vertex_boundary_distances.get(v1);
|
|
300
|
+
const d2 = vertex_boundary_distances.get(v2);
|
|
301
|
+
|
|
302
|
+
// If the edge strictly crosses the erosion threshold
|
|
303
|
+
const crosses_erosion_boundary = (d1 < erode_distance && d2 > erode_distance) || (d2 < erode_distance && d1 > erode_distance);
|
|
304
|
+
|
|
305
|
+
if (crosses_erosion_boundary) {
|
|
306
|
+
// Calculate exact interpolation parameter
|
|
307
|
+
const t = clamp01(inverseLerp(d1, d2, erode_distance));
|
|
308
|
+
edges_to_split.push({ e, t });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Execute splits
|
|
313
|
+
for (const { e, t } of edges_to_split) {
|
|
314
|
+
// splits the edge as well as adjacent faces.
|
|
315
|
+
bt_edge_split(mesh, e, t);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Cull Dead Geometry
|
|
319
|
+
// Any vertex that had a distance < erode_distance is dead.
|
|
320
|
+
// Any face connected to a dead vertex must be destroyed.
|
|
321
|
+
/**
|
|
322
|
+
*
|
|
323
|
+
* @type {Set<number>}
|
|
324
|
+
*/
|
|
325
|
+
const faces_to_kill = new Set();
|
|
326
|
+
|
|
327
|
+
for (const [vertex, distance] of vertex_boundary_distances.entries()) {
|
|
328
|
+
const EPSILON = 1e-17;
|
|
329
|
+
|
|
330
|
+
if (distance >= erode_distance - EPSILON) {
|
|
331
|
+
// too far, survives
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const e_first = mesh.vertex_read_edge(vertex);
|
|
336
|
+
|
|
337
|
+
if (e_first === NULL_POINTER) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let e_curr = e_first;
|
|
342
|
+
do {
|
|
343
|
+
// Traverse radial loops to get all connected faces
|
|
344
|
+
const l_first = mesh.edge_read_loop(e_curr);
|
|
345
|
+
if (l_first !== NULL_POINTER) {
|
|
346
|
+
let l_curr = l_first;
|
|
347
|
+
do {
|
|
348
|
+
faces_to_kill.add(mesh.loop_read_face(l_curr));
|
|
349
|
+
l_curr = mesh.loop_read_radial_next(l_curr);
|
|
350
|
+
} while (l_curr !== l_first);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Move around disk cycle
|
|
354
|
+
const v1 = mesh.edge_read_vertex1(e_curr);
|
|
355
|
+
e_curr = (v1 === vertex) ? mesh.edge_read_v1_disk_next(e_curr) : mesh.edge_read_v2_disk_next(e_curr);
|
|
356
|
+
} while (e_curr !== e_first && e_curr !== NULL_POINTER);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Eradicate the dead faces
|
|
360
|
+
for (const f of faces_to_kill) {
|
|
361
|
+
bt_face_kill(mesh, f);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 6. Cleanup floating vertices and edges
|
|
365
|
+
if (faces_to_kill.size > 0) {
|
|
366
|
+
bt_mesh_cleanup_faceless_references(mesh);
|
|
367
|
+
}
|
|
368
|
+
}
|