@woosh/meep-engine 2.133.3 → 2.134.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/bundle-worker-image-decoder.js +1 -1
- package/package.json +2 -2
- package/src/core/binary/BinaryBuffer.d.ts +20 -0
- package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
- package/src/core/binary/BinaryBuffer.js +50 -0
- package/src/core/binary/BitSet.d.ts +6 -0
- package/src/core/binary/BitSet.d.ts.map +1 -1
- package/src/core/binary/BitSet.js +38 -1
- package/src/core/binary/operations/bitCount.d.ts.map +1 -1
- package/src/core/binary/operations/bitCount.js +11 -6
- package/src/core/geom/3d/aabb/AABB3.d.ts +7 -1
- package/src/core/geom/3d/aabb/AABB3.d.ts.map +1 -1
- package/src/core/geom/3d/aabb/AABB3.js +14 -1
- package/src/core/geom/3d/topology/simplify/EdgeCollapseCandidate.d.ts +4 -4
- package/src/core/geom/3d/topology/simplify/EdgeCollapseCandidate.d.ts.map +1 -1
- package/src/core/geom/3d/topology/simplify/EdgeCollapseCandidate.js +2 -2
- package/src/core/geom/3d/topology/simplify/build_edge_collapse_candidates.d.ts +2 -2
- package/src/core/geom/3d/topology/simplify/build_edge_collapse_candidates.d.ts.map +1 -1
- package/src/core/geom/3d/topology/simplify/build_edge_collapse_candidates.js +1 -1
- package/src/core/geom/3d/topology/simplify/computeEdgeCollapseCost.d.ts +2 -2
- package/src/core/geom/3d/topology/simplify/computeEdgeCollapseCost.d.ts.map +1 -1
- package/src/core/geom/3d/topology/simplify/computeEdgeCollapseCost.js +1 -1
- package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.d.ts +4 -4
- package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.d.ts.map +1 -1
- package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.js +3 -3
- package/src/core/geom/3d/topology/simplify/quadratic/{Quadratic3.d.ts → Quadric3.d.ts} +55 -29
- package/src/core/geom/3d/topology/simplify/quadratic/Quadric3.d.ts.map +1 -0
- package/src/core/geom/3d/topology/simplify/quadratic/Quadric3.js +307 -0
- package/src/core/geom/3d/topology/simplify/quadratic/build_vertex_quadratics.d.ts +7 -7
- package/src/core/geom/3d/topology/simplify/quadratic/build_vertex_quadratics.d.ts.map +1 -1
- package/src/core/geom/3d/topology/simplify/quadratic/build_vertex_quadratics.js +39 -13
- package/src/core/geom/3d/topology/simplify/quadratic/compute_edge_collapse_cost_quadratic.d.ts +2 -2
- package/src/core/geom/3d/topology/simplify/quadratic/compute_edge_collapse_cost_quadratic.d.ts.map +1 -1
- package/src/core/geom/3d/topology/simplify/quadratic/compute_edge_collapse_cost_quadratic.js +1 -1
- package/src/core/geom/3d/topology/simplify/simplifyTopoMesh.d.ts +2 -2
- package/src/core/geom/3d/topology/simplify/simplifyTopoMesh.d.ts.map +1 -1
- package/src/core/geom/3d/topology/simplify/simplifyTopoMesh.js +2 -2
- package/src/core/geom/3d/topology/simplify/simplifyTopoMesh2.d.ts +4 -4
- package/src/core/geom/3d/topology/simplify/simplifyTopoMesh2.d.ts.map +1 -1
- package/src/core/geom/3d/topology/simplify/simplifyTopoMesh2.js +5 -5
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.d.ts +8 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js +56 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.d.ts +14 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.js +95 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts +17 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +352 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.d.ts +21 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.js +52 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.d.ts +16 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js +90 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.d.ts +19 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js +83 -0
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.d.ts +22 -0
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.js +148 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_incenter.js +4 -4
- package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.d.ts +15 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.d.ts.map +1 -0
- package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.js +48 -0
- package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/Terrain.js +6 -1
- package/src/engine/ecs/terrain/ecs/layers/TerrainLayer.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/layers/TerrainLayer.js +10 -9
- package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.js +14 -11
- package/src/engine/graphics/ecs/water/WaterSystem.d.ts.map +1 -1
- package/src/engine/graphics/ecs/water/WaterSystem.js +3 -0
- package/src/engine/graphics/material/SplatMaterial.d.ts.map +1 -1
- package/src/engine/graphics/material/SplatMaterial.js +2 -4
- package/src/engine/graphics/shaders/TerrainShader.js +4 -11
- package/src/engine/navigation/mesh/NavigationMesh.d.ts +6 -4
- package/src/engine/navigation/mesh/NavigationMesh.d.ts.map +1 -1
- package/src/engine/navigation/mesh/NavigationMesh.js +212 -190
- package/src/engine/navigation/mesh/build/navmesh_build_topology.d.ts.map +1 -1
- package/src/engine/navigation/mesh/build/navmesh_build_topology.js +20 -7
- package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts +15 -0
- package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts.map +1 -0
- package/src/engine/navigation/mesh/bvh_query_nearest_face.js +131 -0
- package/src/engine/physics/gjk/gjk.d.ts +16 -0
- package/src/engine/physics/gjk/gjk.d.ts.map +1 -0
- package/src/engine/physics/gjk/gjk.js +378 -0
- package/src/core/geom/3d/topology/simplify/quadratic/Quadratic3.d.ts.map +0 -1
- package/src/core/geom/3d/topology/simplify/quadratic/Quadratic3.js +0 -302
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bt_mesh_compute_vertex_quadratics.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.js"],"names":[],"mappings":"AAWA;;;;;;;;;;GAUG;AACH,yEAHa,QAAQ,EAAE,CA2EtB;yBA3FwB,yCAAyC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { assert } from "../../../../../../assert.js";
|
|
2
|
+
import { v3_dot } from "../../../../../vec3/v3_dot.js";
|
|
3
|
+
import { v3_compute_triangle_normal } from "../../../../triangle/v3_compute_triangle_normal.js";
|
|
4
|
+
import { Quadric3 } from "../../../simplify/quadratic/Quadric3.js";
|
|
5
|
+
import { NULL_POINTER } from "../BinaryTopology.js";
|
|
6
|
+
|
|
7
|
+
const scratch_coord_a = new Float32Array(3);
|
|
8
|
+
const scratch_coord_b = new Float32Array(3);
|
|
9
|
+
const scratch_coord_c = new Float32Array(3);
|
|
10
|
+
const scratch_normal = new Float32Array(3);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Compute a per-vertex quadric error metric from every allocated triangular face.
|
|
14
|
+
*
|
|
15
|
+
* For each triangular face the supporting plane (n, d) is derived from its loop
|
|
16
|
+
* vertices; the corresponding quadric is then accumulated onto each of those
|
|
17
|
+
* three vertices' Quadric3. Non-triangular faces are skipped.
|
|
18
|
+
*
|
|
19
|
+
* @param {BinaryTopology} mesh
|
|
20
|
+
* @returns {Quadric3[]} Array indexed by vertex ID. Entries for unallocated
|
|
21
|
+
* vertex slots are `undefined`.
|
|
22
|
+
*/
|
|
23
|
+
export function bt_mesh_compute_vertex_quadratics(mesh) {
|
|
24
|
+
assert.defined(mesh, 'mesh');
|
|
25
|
+
assert.notNull(mesh, 'mesh');
|
|
26
|
+
|
|
27
|
+
const vertex_pool = mesh.vertices;
|
|
28
|
+
const vertex_count = vertex_pool.size;
|
|
29
|
+
|
|
30
|
+
const quadratics = new Array(vertex_count);
|
|
31
|
+
|
|
32
|
+
for (let v = 0; v < vertex_count; v++) {
|
|
33
|
+
if (vertex_pool.is_allocated(v)) {
|
|
34
|
+
quadratics[v] = new Quadric3();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const face_pool = mesh.faces;
|
|
39
|
+
const face_count = face_pool.size;
|
|
40
|
+
|
|
41
|
+
const q = new Quadric3();
|
|
42
|
+
|
|
43
|
+
for (let f = 0; f < face_count; f++) {
|
|
44
|
+
if (!face_pool.is_allocated(f)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const la = mesh.face_read_loop(f);
|
|
49
|
+
|
|
50
|
+
if (la === NULL_POINTER) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const lb = mesh.loop_read_next(la);
|
|
55
|
+
const lc = mesh.loop_read_next(lb);
|
|
56
|
+
|
|
57
|
+
// Skip non-triangles
|
|
58
|
+
if (mesh.loop_read_next(lc) !== la) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const va = mesh.loop_read_vertex(la);
|
|
63
|
+
const vb = mesh.loop_read_vertex(lb);
|
|
64
|
+
const vc = mesh.loop_read_vertex(lc);
|
|
65
|
+
|
|
66
|
+
mesh.vertex_read_coordinate(scratch_coord_a, 0, va);
|
|
67
|
+
mesh.vertex_read_coordinate(scratch_coord_b, 0, vb);
|
|
68
|
+
mesh.vertex_read_coordinate(scratch_coord_c, 0, vc);
|
|
69
|
+
|
|
70
|
+
v3_compute_triangle_normal(
|
|
71
|
+
scratch_normal, 0,
|
|
72
|
+
scratch_coord_a[0], scratch_coord_a[1], scratch_coord_a[2],
|
|
73
|
+
scratch_coord_b[0], scratch_coord_b[1], scratch_coord_b[2],
|
|
74
|
+
scratch_coord_c[0], scratch_coord_c[1], scratch_coord_c[2]
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const nx = scratch_normal[0];
|
|
78
|
+
const ny = scratch_normal[1];
|
|
79
|
+
const nz = scratch_normal[2];
|
|
80
|
+
|
|
81
|
+
// Plane offset: d = -(n . v_a)
|
|
82
|
+
const d = -v3_dot(
|
|
83
|
+
nx, ny, nz,
|
|
84
|
+
scratch_coord_a[0], scratch_coord_a[1], scratch_coord_a[2]
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
q.setFromVector4(nx, ny, nz, d);
|
|
88
|
+
|
|
89
|
+
quadratics[va].add(q);
|
|
90
|
+
quadratics[vb].add(q);
|
|
91
|
+
quadratics[vc].add(q);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return quadratics;
|
|
95
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplify a mesh in-place by greedily collapsing the cheapest edges (per a quadric
|
|
3
|
+
* error metric) until the allocated face count drops to `target_face_count`.
|
|
4
|
+
*
|
|
5
|
+
* Vertices listed in `restricted_vertices` are pinned: they are neither moved nor
|
|
6
|
+
* removed. If the target cannot be reached (e.g. too many restricted vertices),
|
|
7
|
+
* simplification stops early without failing.
|
|
8
|
+
*
|
|
9
|
+
* At the end of the call the mesh is cleaned up so that no dangling loops, edges,
|
|
10
|
+
* or vertices remain in the topology.
|
|
11
|
+
*
|
|
12
|
+
* @param {BinaryTopology} mesh Mesh to simplify in-place.
|
|
13
|
+
* @param {number} target_face_count Desired face count after simplification.
|
|
14
|
+
* @param {Set<number>} [restricted_vertices] Vertices that must not move or be removed.
|
|
15
|
+
*/
|
|
16
|
+
export function bt_mesh_simplify(mesh: BinaryTopology, target_face_count: number, restricted_vertices?: Set<number>): void;
|
|
17
|
+
//# sourceMappingURL=bt_mesh_simplify.d.ts.map
|
|
@@ -0,0 +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":"AAqLA;;;;;;;;;;;;;;GAcG;AACH,0EAHW,MAAM,wBACN,IAAI,MAAM,CAAC,QA2HrB"}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { assert } from "../../../../../../assert.js";
|
|
2
|
+
import { Uint32Heap } from "../../../../../../collection/heap/Uint32Heap.js";
|
|
3
|
+
import { Quadric3 } from "../../../simplify/quadratic/Quadric3.js";
|
|
4
|
+
import { NULL_POINTER } from "../BinaryTopology.js";
|
|
5
|
+
import { bt_loop_kill } from "./bt_loop_kill.js";
|
|
6
|
+
import { bt_mesh_compute_vertex_quadratics } from "./bt_mesh_compute_vertex_quadratics.js";
|
|
7
|
+
import { bt_edge_collapse } from "./edge/bt_edge_collapse.js";
|
|
8
|
+
import { bt_edge_kill } from "./edge/bt_edge_kill.js";
|
|
9
|
+
import { bt_edge_kill_parallels } from "./edge/bt_edge_kill_parallels.js";
|
|
10
|
+
import { bt_vert_fuse_duplicate_edges } from "./vertex/bt_vert_fuse_duplicate_edges.js";
|
|
11
|
+
import { bt_vert_kill } from "./vertex/bt_vert_kill.js";
|
|
12
|
+
|
|
13
|
+
// Module-level scratch buffers to keep GC pressure low across calls.
|
|
14
|
+
const scratch_coord_a = new Float32Array(3);
|
|
15
|
+
const scratch_coord_b = new Float32Array(3);
|
|
16
|
+
const scratch_target = new Float32Array(3);
|
|
17
|
+
const scratch_combined_q = new Quadric3();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Count the number of still-live faces attached to an edge via its radial loop cycle.
|
|
21
|
+
* Orphan loops (whose face was already killed by a prior collapse) are skipped so
|
|
22
|
+
* that callers tracking running face totals don't double-decrement.
|
|
23
|
+
*
|
|
24
|
+
* @param {BinaryTopology} mesh
|
|
25
|
+
* @param {number} edge_id
|
|
26
|
+
* @returns {number}
|
|
27
|
+
*/
|
|
28
|
+
function count_edge_faces(mesh, edge_id) {
|
|
29
|
+
const l0 = mesh.edge_read_loop(edge_id);
|
|
30
|
+
|
|
31
|
+
if (l0 === NULL_POINTER) {
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const face_pool = mesh.faces;
|
|
36
|
+
|
|
37
|
+
let count = 0;
|
|
38
|
+
let l = l0;
|
|
39
|
+
|
|
40
|
+
do {
|
|
41
|
+
const f = mesh.loop_read_face(l);
|
|
42
|
+
|
|
43
|
+
if (f !== NULL_POINTER && face_pool.is_allocated(f)) {
|
|
44
|
+
count++;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
l = mesh.loop_read_radial_next(l);
|
|
48
|
+
} while (l !== l0 && l !== NULL_POINTER);
|
|
49
|
+
|
|
50
|
+
return count;
|
|
51
|
+
}
|
|
52
|
+
|
|
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
|
+
/**
|
|
79
|
+
* Evaluate a potential edge collapse: choose the optimal surviving vertex position
|
|
80
|
+
* and return the quadric-based cost. Returns Infinity if the edge cannot be collapsed.
|
|
81
|
+
*
|
|
82
|
+
* @param {BinaryTopology} mesh
|
|
83
|
+
* @param {number} edge_id
|
|
84
|
+
* @param {Quadric3[]} quadratics
|
|
85
|
+
* @param {Set<number>} restricted_vertices
|
|
86
|
+
* @param {Float32Array|number[]} out_target Target position is written here.
|
|
87
|
+
* @returns {number}
|
|
88
|
+
*/
|
|
89
|
+
function evaluate_edge_collapse(mesh, edge_id, quadratics, restricted_vertices, out_target) {
|
|
90
|
+
const v1 = mesh.edge_read_vertex1(edge_id);
|
|
91
|
+
const v2 = mesh.edge_read_vertex2(edge_id);
|
|
92
|
+
|
|
93
|
+
if (v1 === v2) {
|
|
94
|
+
// Degenerate self-loop, not collapsible
|
|
95
|
+
return Number.POSITIVE_INFINITY;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const v1_restricted = restricted_vertices.has(v1);
|
|
99
|
+
const v2_restricted = restricted_vertices.has(v2);
|
|
100
|
+
|
|
101
|
+
if (v1_restricted && v2_restricted) {
|
|
102
|
+
return Number.POSITIVE_INFINITY;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const q1 = quadratics[v1];
|
|
106
|
+
const q2 = quadratics[v2];
|
|
107
|
+
|
|
108
|
+
// Missing quadrics mean at least one endpoint was a prior victim that somehow
|
|
109
|
+
// persisted as a topology endpoint (e.g. via a self-loop produced from parallel
|
|
110
|
+
// edges). Such edges are not useful collapse candidates.
|
|
111
|
+
if (q1 === null || q2 === null || q1 === undefined || q2 === undefined) {
|
|
112
|
+
return Number.POSITIVE_INFINITY;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (v1_restricted) {
|
|
116
|
+
mesh.vertex_read_coordinate(out_target, 0, v1);
|
|
117
|
+
} else if (v2_restricted) {
|
|
118
|
+
mesh.vertex_read_coordinate(out_target, 0, v2);
|
|
119
|
+
} else {
|
|
120
|
+
scratch_combined_q.copy(q1);
|
|
121
|
+
scratch_combined_q.add(q2);
|
|
122
|
+
|
|
123
|
+
if (!scratch_combined_q.optimize(out_target)) {
|
|
124
|
+
// Singular combined tensor: fall back to the midpoint
|
|
125
|
+
mesh.vertex_read_coordinate(scratch_coord_a, 0, v1);
|
|
126
|
+
mesh.vertex_read_coordinate(scratch_coord_b, 0, v2);
|
|
127
|
+
|
|
128
|
+
out_target[0] = (scratch_coord_a[0] + scratch_coord_b[0]) * 0.5;
|
|
129
|
+
out_target[1] = (scratch_coord_a[1] + scratch_coord_b[1]) * 0.5;
|
|
130
|
+
out_target[2] = (scratch_coord_a[2] + scratch_coord_b[2]) * 0.5;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const tx = out_target[0];
|
|
135
|
+
const ty = out_target[1];
|
|
136
|
+
const tz = out_target[2];
|
|
137
|
+
|
|
138
|
+
// Cost can drift slightly negative for near-zero values due to fp noise.
|
|
139
|
+
return Math.abs(q1.evaluate(tx, ty, tz) + q2.evaluate(tx, ty, tz));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Re-score every edge in the disk cycle of `survivor` that is currently in the heap.
|
|
144
|
+
* Edges that were never inserted (e.g. both endpoints restricted) are left untouched.
|
|
145
|
+
*
|
|
146
|
+
* @param {BinaryTopology} mesh
|
|
147
|
+
* @param {number} survivor
|
|
148
|
+
* @param {Quadric3[]} quadratics
|
|
149
|
+
* @param {Set<number>} restricted_vertices
|
|
150
|
+
* @param {Uint32Heap} heap
|
|
151
|
+
*/
|
|
152
|
+
function update_neighbor_costs(mesh, survivor, quadratics, restricted_vertices, heap) {
|
|
153
|
+
let curr_e = mesh.vertex_read_edge(survivor);
|
|
154
|
+
|
|
155
|
+
if (curr_e === NULL_POINTER) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const start_e = curr_e;
|
|
160
|
+
|
|
161
|
+
do {
|
|
162
|
+
// Grab the next edge in the disk cycle before we do anything that might
|
|
163
|
+
// make `curr_e` harder to reason about (the cycle itself is not mutated).
|
|
164
|
+
let next_e;
|
|
165
|
+
if (mesh.edge_read_vertex1(curr_e) === survivor) {
|
|
166
|
+
next_e = mesh.edge_read_v1_disk_next(curr_e);
|
|
167
|
+
} else {
|
|
168
|
+
next_e = mesh.edge_read_v2_disk_next(curr_e);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const idx = heap.find_index_by_id(curr_e);
|
|
172
|
+
|
|
173
|
+
if (idx !== -1) {
|
|
174
|
+
const cost = evaluate_edge_collapse(mesh, curr_e, quadratics, restricted_vertices, scratch_target);
|
|
175
|
+
heap.__update_score_by_index(idx, cost);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
curr_e = next_e;
|
|
179
|
+
} while (curr_e !== start_e && curr_e !== NULL_POINTER);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Simplify a mesh in-place by greedily collapsing the cheapest edges (per a quadric
|
|
184
|
+
* error metric) until the allocated face count drops to `target_face_count`.
|
|
185
|
+
*
|
|
186
|
+
* Vertices listed in `restricted_vertices` are pinned: they are neither moved nor
|
|
187
|
+
* removed. If the target cannot be reached (e.g. too many restricted vertices),
|
|
188
|
+
* simplification stops early without failing.
|
|
189
|
+
*
|
|
190
|
+
* At the end of the call the mesh is cleaned up so that no dangling loops, edges,
|
|
191
|
+
* or vertices remain in the topology.
|
|
192
|
+
*
|
|
193
|
+
* @param {BinaryTopology} mesh Mesh to simplify in-place.
|
|
194
|
+
* @param {number} target_face_count Desired face count after simplification.
|
|
195
|
+
* @param {Set<number>} [restricted_vertices] Vertices that must not move or be removed.
|
|
196
|
+
*/
|
|
197
|
+
export function bt_mesh_simplify(
|
|
198
|
+
mesh,
|
|
199
|
+
target_face_count,
|
|
200
|
+
restricted_vertices = new Set()
|
|
201
|
+
) {
|
|
202
|
+
assert.defined(mesh, 'mesh');
|
|
203
|
+
assert.notNull(mesh, 'mesh');
|
|
204
|
+
assert.equal(mesh.isBinaryTopology, true, 'mesh.isBinaryTopology !== true');
|
|
205
|
+
assert.isNonNegativeInteger(target_face_count, 'target_face_count');
|
|
206
|
+
assert.defined(restricted_vertices, 'restricted_vertices');
|
|
207
|
+
assert.notNull(restricted_vertices, 'restricted_vertices');
|
|
208
|
+
|
|
209
|
+
const face_pool = mesh.faces;
|
|
210
|
+
|
|
211
|
+
// Count allocated faces up front so we can track progress without scanning each iteration.
|
|
212
|
+
let current_face_count = 0;
|
|
213
|
+
for (let f = 0; f < face_pool.size; f++) {
|
|
214
|
+
if (face_pool.is_allocated(f)) {
|
|
215
|
+
current_face_count++;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (current_face_count <= target_face_count) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const quadratics = bt_mesh_compute_vertex_quadratics(mesh);
|
|
224
|
+
|
|
225
|
+
const edge_pool = mesh.edges;
|
|
226
|
+
const heap = new Uint32Heap(edge_pool.size);
|
|
227
|
+
|
|
228
|
+
for (let e = 0; e < edge_pool.size; e++) {
|
|
229
|
+
if (!edge_pool.is_allocated(e)) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const cost = evaluate_edge_collapse(mesh, e, quadratics, restricted_vertices, scratch_target);
|
|
234
|
+
|
|
235
|
+
if (cost === Number.POSITIVE_INFINITY) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
heap.insert(e, cost);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Safety bound: every real collapse removes at least one face; the extra headroom
|
|
243
|
+
// accommodates no-op collapses on orphan edges that may appear after a collapse
|
|
244
|
+
// produces duplicate parallel edges.
|
|
245
|
+
const max_iterations = current_face_count * 10 + heap.size + 10;
|
|
246
|
+
let iterations = 0;
|
|
247
|
+
|
|
248
|
+
while (
|
|
249
|
+
!heap.is_empty()
|
|
250
|
+
&& current_face_count > target_face_count
|
|
251
|
+
&& iterations < max_iterations
|
|
252
|
+
) {
|
|
253
|
+
iterations++;
|
|
254
|
+
|
|
255
|
+
const edge_id = heap.pop_min();
|
|
256
|
+
|
|
257
|
+
if (!edge_pool.is_allocated(edge_id)) {
|
|
258
|
+
// Edge was killed by an earlier collapse (e.g. adjacent face death).
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const v1 = mesh.edge_read_vertex1(edge_id);
|
|
263
|
+
const v2 = mesh.edge_read_vertex2(edge_id);
|
|
264
|
+
|
|
265
|
+
if (v1 === v2) {
|
|
266
|
+
// Degenerate self-loop produced by a prior collapse of parallel edges.
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const v1_restricted = restricted_vertices.has(v1);
|
|
271
|
+
const v2_restricted = restricted_vertices.has(v2);
|
|
272
|
+
|
|
273
|
+
if (v1_restricted && v2_restricted) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const cost = evaluate_edge_collapse(mesh, edge_id, quadratics, restricted_vertices, scratch_target);
|
|
278
|
+
|
|
279
|
+
if (cost === Number.POSITIVE_INFINITY) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const face_deaths = count_edge_faces(mesh, edge_id);
|
|
284
|
+
|
|
285
|
+
// bt_edge_collapse keeps v1 as the survivor. If v2 is the pinned endpoint,
|
|
286
|
+
// swap the edge's slots so the pinned vertex ends up in the v1 position.
|
|
287
|
+
if (v2_restricted) {
|
|
288
|
+
swap_edge_vertex_slots(mesh, edge_id);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const pre_survivor = mesh.edge_read_vertex1(edge_id);
|
|
292
|
+
const pre_victim = mesh.edge_read_vertex2(edge_id);
|
|
293
|
+
|
|
294
|
+
// Accumulate victim's quadric onto the survivor before the topology mutates.
|
|
295
|
+
quadratics[pre_survivor].add(quadratics[pre_victim]);
|
|
296
|
+
quadratics[pre_victim] = null;
|
|
297
|
+
|
|
298
|
+
// Kill any direct parallel edges between survivor and victim. Leaving them in
|
|
299
|
+
// place would cause bt_vertex_replace (inside bt_edge_collapse) to produce a
|
|
300
|
+
// self-loop edge (surv, surv), which corrupts the survivor's disk cycle.
|
|
301
|
+
current_face_count -= bt_edge_kill_parallels(mesh, edge_id);
|
|
302
|
+
|
|
303
|
+
const survivor = bt_edge_collapse(mesh, edge_id, scratch_target, 0);
|
|
304
|
+
|
|
305
|
+
current_face_count -= face_deaths;
|
|
306
|
+
|
|
307
|
+
// Link-vertex parallels produced by this collapse will become direct parallels
|
|
308
|
+
// at the next collapse that touches them, so fuse them now while disk cycles
|
|
309
|
+
// are still intact.
|
|
310
|
+
current_face_count -= bt_vert_fuse_duplicate_edges(mesh, survivor);
|
|
311
|
+
|
|
312
|
+
update_neighbor_costs(mesh, survivor, quadratics, restricted_vertices, heap);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Drop any loose loops/edges/verts produced during collapses, preserving
|
|
316
|
+
// restricted vertices even if they ended up isolated.
|
|
317
|
+
cleanup_faceless_preserving_restricted(mesh, restricted_vertices);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Variant of bt_mesh_cleanup_faceless_references that never releases a restricted
|
|
322
|
+
* vertex even when it has no remaining edges.
|
|
323
|
+
*
|
|
324
|
+
* @param {BinaryTopology} mesh
|
|
325
|
+
* @param {Set<number>} restricted_vertices
|
|
326
|
+
*/
|
|
327
|
+
function cleanup_faceless_preserving_restricted(mesh, restricted_vertices) {
|
|
328
|
+
const loop_pool = mesh.loops;
|
|
329
|
+
for (let l = 0; l < loop_pool.size; l++) {
|
|
330
|
+
if (!loop_pool.is_allocated(l)) continue;
|
|
331
|
+
if (mesh.loop_read_face(l) === NULL_POINTER) {
|
|
332
|
+
bt_loop_kill(mesh, l);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const edge_pool = mesh.edges;
|
|
337
|
+
for (let e = 0; e < edge_pool.size; e++) {
|
|
338
|
+
if (!edge_pool.is_allocated(e)) continue;
|
|
339
|
+
if (mesh.edge_read_loop(e) === NULL_POINTER) {
|
|
340
|
+
bt_edge_kill(mesh, e);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const vertex_pool = mesh.vertices;
|
|
345
|
+
for (let v = 0; v < vertex_pool.size; v++) {
|
|
346
|
+
if (!vertex_pool.is_allocated(v)) continue;
|
|
347
|
+
if (restricted_vertices.has(v)) continue;
|
|
348
|
+
if (mesh.vertex_read_edge(v) === NULL_POINTER) {
|
|
349
|
+
bt_vert_kill(mesh, v);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collapses an edge by merging its two vertices into one, relocating the surviving
|
|
3
|
+
* vertex to the supplied coordinate.
|
|
4
|
+
*
|
|
5
|
+
* All faces incident to the collapse edge are removed (they would become degenerate),
|
|
6
|
+
* while faces that only share one of the endpoints are retained and re-stitched onto
|
|
7
|
+
* the surviving vertex. The victim vertex is released.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: Does not perform a weld pass, so collapses that share "link" vertices between
|
|
10
|
+
* the two endpoints will produce duplicate (parallel) edges. The topology remains
|
|
11
|
+
* self-consistent (disk and radial cycles are intact), but callers doing mesh
|
|
12
|
+
* simplification typically want to run a cleanup pass afterward.
|
|
13
|
+
*
|
|
14
|
+
* @param {BinaryTopology} mesh
|
|
15
|
+
* @param {number} edge_id The edge to collapse
|
|
16
|
+
* @param {number[]|Float32Array} new_position New coordinate for the surviving vertex
|
|
17
|
+
* @param {number} [new_position_offset] Offset into `new_position`
|
|
18
|
+
* @returns {number} The surviving vertex ID
|
|
19
|
+
*/
|
|
20
|
+
export function bt_edge_collapse(mesh: BinaryTopology, edge_id: number, new_position: number[] | Float32Array, new_position_offset?: number): number;
|
|
21
|
+
//# sourceMappingURL=bt_edge_collapse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bt_edge_collapse.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;GAkBG;AACH,gEALW,MAAM,gBACN,MAAM,EAAE,GAAC,YAAY,wBACrB,MAAM,GACJ,MAAM,CA6BlB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { assert } from "../../../../../../../assert.js";
|
|
2
|
+
import { bt_vert_kill } from "../vertex/bt_vert_kill.js";
|
|
3
|
+
import { bt_vertex_replace } from "../vertex/bt_vertex_replace.js";
|
|
4
|
+
import { bt_edge_kill } from "./bt_edge_kill.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Collapses an edge by merging its two vertices into one, relocating the surviving
|
|
8
|
+
* vertex to the supplied coordinate.
|
|
9
|
+
*
|
|
10
|
+
* All faces incident to the collapse edge are removed (they would become degenerate),
|
|
11
|
+
* while faces that only share one of the endpoints are retained and re-stitched onto
|
|
12
|
+
* the surviving vertex. The victim vertex is released.
|
|
13
|
+
*
|
|
14
|
+
* NOTE: Does not perform a weld pass, so collapses that share "link" vertices between
|
|
15
|
+
* the two endpoints will produce duplicate (parallel) edges. The topology remains
|
|
16
|
+
* self-consistent (disk and radial cycles are intact), but callers doing mesh
|
|
17
|
+
* simplification typically want to run a cleanup pass afterward.
|
|
18
|
+
*
|
|
19
|
+
* @param {BinaryTopology} mesh
|
|
20
|
+
* @param {number} edge_id The edge to collapse
|
|
21
|
+
* @param {number[]|Float32Array} new_position New coordinate for the surviving vertex
|
|
22
|
+
* @param {number} [new_position_offset] Offset into `new_position`
|
|
23
|
+
* @returns {number} The surviving vertex ID
|
|
24
|
+
*/
|
|
25
|
+
export function bt_edge_collapse(
|
|
26
|
+
mesh,
|
|
27
|
+
edge_id,
|
|
28
|
+
new_position,
|
|
29
|
+
new_position_offset = 0
|
|
30
|
+
) {
|
|
31
|
+
assert.defined(mesh, 'mesh');
|
|
32
|
+
assert.notNull(mesh, 'mesh');
|
|
33
|
+
assert.isNonNegativeInteger(edge_id, 'edge_id');
|
|
34
|
+
assert.defined(new_position, 'new_position');
|
|
35
|
+
|
|
36
|
+
const v_survivor = mesh.edge_read_vertex1(edge_id);
|
|
37
|
+
const v_victim = mesh.edge_read_vertex2(edge_id);
|
|
38
|
+
|
|
39
|
+
// 1. Kill adjacent faces and remove the collapse edge itself.
|
|
40
|
+
bt_edge_kill(mesh, edge_id);
|
|
41
|
+
|
|
42
|
+
// 2. Re-point every remaining edge/loop from the victim onto the survivor.
|
|
43
|
+
bt_vertex_replace(mesh, v_victim, v_survivor);
|
|
44
|
+
|
|
45
|
+
// 3. Release the now-disconnected victim vertex.
|
|
46
|
+
bt_vert_kill(mesh, v_victim);
|
|
47
|
+
|
|
48
|
+
// 4. Move the survivor to the requested position.
|
|
49
|
+
mesh.vertex_write_coordinate(v_survivor, new_position, new_position_offset);
|
|
50
|
+
|
|
51
|
+
return v_survivor;
|
|
52
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kill every edge that shares both endpoints with `keep_edge`, leaving `keep_edge`
|
|
3
|
+
* itself alive. Useful as a pre-pass before {@link bt_edge_collapse} so that
|
|
4
|
+
* {@link bt_vertex_replace} cannot produce a self-loop edge when a direct parallel
|
|
5
|
+
* exists.
|
|
6
|
+
*
|
|
7
|
+
* Adjacent faces of each killed parallel are removed as part of bt_edge_kill; the
|
|
8
|
+
* return value reports how many were taken down so callers doing face-count
|
|
9
|
+
* bookkeeping don't have to rescan.
|
|
10
|
+
*
|
|
11
|
+
* @param {BinaryTopology} mesh
|
|
12
|
+
* @param {number} keep_edge Edge ID to preserve; its other-parallels are killed.
|
|
13
|
+
* @returns {number} Number of faces killed as a side effect.
|
|
14
|
+
*/
|
|
15
|
+
export function bt_edge_kill_parallels(mesh: BinaryTopology, keep_edge: number): number;
|
|
16
|
+
//# sourceMappingURL=bt_edge_kill_parallels.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bt_edge_kill_parallels.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AACH,wEAHW,MAAM,GACJ,MAAM,CAyElB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { assert } from "../../../../../../../assert.js";
|
|
2
|
+
import { NULL_POINTER } from "../../BinaryTopology.js";
|
|
3
|
+
import { bt_edge_kill } from "./bt_edge_kill.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Kill every edge that shares both endpoints with `keep_edge`, leaving `keep_edge`
|
|
7
|
+
* itself alive. Useful as a pre-pass before {@link bt_edge_collapse} so that
|
|
8
|
+
* {@link bt_vertex_replace} cannot produce a self-loop edge when a direct parallel
|
|
9
|
+
* exists.
|
|
10
|
+
*
|
|
11
|
+
* Adjacent faces of each killed parallel are removed as part of bt_edge_kill; the
|
|
12
|
+
* return value reports how many were taken down so callers doing face-count
|
|
13
|
+
* bookkeeping don't have to rescan.
|
|
14
|
+
*
|
|
15
|
+
* @param {BinaryTopology} mesh
|
|
16
|
+
* @param {number} keep_edge Edge ID to preserve; its other-parallels are killed.
|
|
17
|
+
* @returns {number} Number of faces killed as a side effect.
|
|
18
|
+
*/
|
|
19
|
+
export function bt_edge_kill_parallels(mesh, keep_edge) {
|
|
20
|
+
assert.defined(mesh, 'mesh');
|
|
21
|
+
assert.notNull(mesh, 'mesh');
|
|
22
|
+
assert.isNonNegativeInteger(keep_edge, 'keep_edge');
|
|
23
|
+
|
|
24
|
+
const a = mesh.edge_read_vertex1(keep_edge);
|
|
25
|
+
const b = mesh.edge_read_vertex2(keep_edge);
|
|
26
|
+
|
|
27
|
+
const start_e = mesh.vertex_read_edge(b);
|
|
28
|
+
|
|
29
|
+
if (start_e === NULL_POINTER) {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Collect parallels while walking b's disk cycle. We snapshot into an array
|
|
34
|
+
// so that subsequent bt_edge_kill calls (which mutate the disk cycle) can't
|
|
35
|
+
// invalidate the traversal.
|
|
36
|
+
const victims = [];
|
|
37
|
+
let curr_e = start_e;
|
|
38
|
+
|
|
39
|
+
do {
|
|
40
|
+
let next_e;
|
|
41
|
+
if (mesh.edge_read_vertex1(curr_e) === b) {
|
|
42
|
+
next_e = mesh.edge_read_v1_disk_next(curr_e);
|
|
43
|
+
} else {
|
|
44
|
+
next_e = mesh.edge_read_v2_disk_next(curr_e);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (curr_e !== keep_edge) {
|
|
48
|
+
const v1 = mesh.edge_read_vertex1(curr_e);
|
|
49
|
+
const v2 = mesh.edge_read_vertex2(curr_e);
|
|
50
|
+
|
|
51
|
+
if ((v1 === a && v2 === b) || (v1 === b && v2 === a)) {
|
|
52
|
+
victims.push(curr_e);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
curr_e = next_e;
|
|
57
|
+
} while (curr_e !== start_e && curr_e !== NULL_POINTER);
|
|
58
|
+
|
|
59
|
+
const edge_pool = mesh.edges;
|
|
60
|
+
const face_pool = mesh.faces;
|
|
61
|
+
|
|
62
|
+
let faces_killed = 0;
|
|
63
|
+
|
|
64
|
+
for (let i = 0; i < victims.length; i++) {
|
|
65
|
+
const e = victims[i];
|
|
66
|
+
|
|
67
|
+
if (!edge_pool.is_allocated(e)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Count still-allocated faces in this edge's radial loop cycle before
|
|
72
|
+
// bt_edge_kill drops them. Orphan loops (face already dead) are skipped.
|
|
73
|
+
const l0 = mesh.edge_read_loop(e);
|
|
74
|
+
|
|
75
|
+
if (l0 !== NULL_POINTER) {
|
|
76
|
+
let l = l0;
|
|
77
|
+
do {
|
|
78
|
+
const f = mesh.loop_read_face(l);
|
|
79
|
+
if (f !== NULL_POINTER && face_pool.is_allocated(f)) {
|
|
80
|
+
faces_killed++;
|
|
81
|
+
}
|
|
82
|
+
l = mesh.loop_read_radial_next(l);
|
|
83
|
+
} while (l !== l0 && l !== NULL_POINTER);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
bt_edge_kill(mesh, e);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return faces_killed;
|
|
90
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walk the mesh and fuse any pair of edges that share the same endpoint pair into a single edge.
|
|
3
|
+
*
|
|
4
|
+
* When two edges connect the same vertices, all loops on the victim are moved into the master's
|
|
5
|
+
* radial cycle, the victim is removed from both endpoint disk cycles, and the victim edge is
|
|
6
|
+
* deallocated. This restores the topology invariant that a vertex pair is represented by at most
|
|
7
|
+
* one edge — which is what loop/face neighbour queries rely on.
|
|
8
|
+
*
|
|
9
|
+
* Degenerate edges (v1 === v2) are left alone; callers that want those removed should run a
|
|
10
|
+
* dedicated pass.
|
|
11
|
+
*
|
|
12
|
+
* Typically used after {@link bt_merge_verts_by_distance}, which re-points vertex references but
|
|
13
|
+
* can leave behind multiple edges sharing the same endpoint pair.
|
|
14
|
+
*
|
|
15
|
+
* @param {BinaryTopology} mesh
|
|
16
|
+
* @returns {number} number of edges fused away
|
|
17
|
+
*/
|
|
18
|
+
export function bt_mesh_fuse_duplicate_edges(mesh: BinaryTopology): number;
|
|
19
|
+
//# sourceMappingURL=bt_mesh_fuse_duplicate_edges.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bt_mesh_fuse_duplicate_edges.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;GAgBG;AACH,oEAFa,MAAM,CA6DlB"}
|