@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.
Files changed (89) hide show
  1. package/build/bundle-worker-image-decoder.js +1 -1
  2. package/package.json +2 -2
  3. package/src/core/binary/BinaryBuffer.d.ts +20 -0
  4. package/src/core/binary/BinaryBuffer.d.ts.map +1 -1
  5. package/src/core/binary/BinaryBuffer.js +50 -0
  6. package/src/core/binary/BitSet.d.ts +6 -0
  7. package/src/core/binary/BitSet.d.ts.map +1 -1
  8. package/src/core/binary/BitSet.js +38 -1
  9. package/src/core/binary/operations/bitCount.d.ts.map +1 -1
  10. package/src/core/binary/operations/bitCount.js +11 -6
  11. package/src/core/geom/3d/aabb/AABB3.d.ts +7 -1
  12. package/src/core/geom/3d/aabb/AABB3.d.ts.map +1 -1
  13. package/src/core/geom/3d/aabb/AABB3.js +14 -1
  14. package/src/core/geom/3d/topology/simplify/EdgeCollapseCandidate.d.ts +4 -4
  15. package/src/core/geom/3d/topology/simplify/EdgeCollapseCandidate.d.ts.map +1 -1
  16. package/src/core/geom/3d/topology/simplify/EdgeCollapseCandidate.js +2 -2
  17. package/src/core/geom/3d/topology/simplify/build_edge_collapse_candidates.d.ts +2 -2
  18. package/src/core/geom/3d/topology/simplify/build_edge_collapse_candidates.d.ts.map +1 -1
  19. package/src/core/geom/3d/topology/simplify/build_edge_collapse_candidates.js +1 -1
  20. package/src/core/geom/3d/topology/simplify/computeEdgeCollapseCost.d.ts +2 -2
  21. package/src/core/geom/3d/topology/simplify/computeEdgeCollapseCost.d.ts.map +1 -1
  22. package/src/core/geom/3d/topology/simplify/computeEdgeCollapseCost.js +1 -1
  23. package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.d.ts +4 -4
  24. package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.d.ts.map +1 -1
  25. package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.js +3 -3
  26. package/src/core/geom/3d/topology/simplify/quadratic/{Quadratic3.d.ts → Quadric3.d.ts} +55 -29
  27. package/src/core/geom/3d/topology/simplify/quadratic/Quadric3.d.ts.map +1 -0
  28. package/src/core/geom/3d/topology/simplify/quadratic/Quadric3.js +307 -0
  29. package/src/core/geom/3d/topology/simplify/quadratic/build_vertex_quadratics.d.ts +7 -7
  30. package/src/core/geom/3d/topology/simplify/quadratic/build_vertex_quadratics.d.ts.map +1 -1
  31. package/src/core/geom/3d/topology/simplify/quadratic/build_vertex_quadratics.js +39 -13
  32. package/src/core/geom/3d/topology/simplify/quadratic/compute_edge_collapse_cost_quadratic.d.ts +2 -2
  33. package/src/core/geom/3d/topology/simplify/quadratic/compute_edge_collapse_cost_quadratic.d.ts.map +1 -1
  34. package/src/core/geom/3d/topology/simplify/quadratic/compute_edge_collapse_cost_quadratic.js +1 -1
  35. package/src/core/geom/3d/topology/simplify/simplifyTopoMesh.d.ts +2 -2
  36. package/src/core/geom/3d/topology/simplify/simplifyTopoMesh.d.ts.map +1 -1
  37. package/src/core/geom/3d/topology/simplify/simplifyTopoMesh.js +2 -2
  38. package/src/core/geom/3d/topology/simplify/simplifyTopoMesh2.d.ts +4 -4
  39. package/src/core/geom/3d/topology/simplify/simplifyTopoMesh2.d.ts.map +1 -1
  40. package/src/core/geom/3d/topology/simplify/simplifyTopoMesh2.js +5 -5
  41. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.d.ts +8 -0
  42. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.d.ts.map +1 -0
  43. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js +56 -0
  44. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.d.ts +14 -0
  45. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.d.ts.map +1 -0
  46. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.js +95 -0
  47. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts +17 -0
  48. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.d.ts.map +1 -0
  49. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_simplify.js +352 -0
  50. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.d.ts +21 -0
  51. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.d.ts.map +1 -0
  52. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.js +52 -0
  53. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.d.ts +16 -0
  54. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.d.ts.map +1 -0
  55. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js +90 -0
  56. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.d.ts +19 -0
  57. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.d.ts.map +1 -0
  58. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js +83 -0
  59. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.d.ts +22 -0
  60. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.d.ts.map +1 -0
  61. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.js +148 -0
  62. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_incenter.js +4 -4
  63. package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.d.ts +15 -0
  64. package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.d.ts.map +1 -0
  65. package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.js +48 -0
  66. package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
  67. package/src/engine/ecs/terrain/ecs/Terrain.js +6 -1
  68. package/src/engine/ecs/terrain/ecs/layers/TerrainLayer.d.ts.map +1 -1
  69. package/src/engine/ecs/terrain/ecs/layers/TerrainLayer.js +10 -9
  70. package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.d.ts.map +1 -1
  71. package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.js +14 -11
  72. package/src/engine/graphics/ecs/water/WaterSystem.d.ts.map +1 -1
  73. package/src/engine/graphics/ecs/water/WaterSystem.js +3 -0
  74. package/src/engine/graphics/material/SplatMaterial.d.ts.map +1 -1
  75. package/src/engine/graphics/material/SplatMaterial.js +2 -4
  76. package/src/engine/graphics/shaders/TerrainShader.js +4 -11
  77. package/src/engine/navigation/mesh/NavigationMesh.d.ts +6 -4
  78. package/src/engine/navigation/mesh/NavigationMesh.d.ts.map +1 -1
  79. package/src/engine/navigation/mesh/NavigationMesh.js +212 -190
  80. package/src/engine/navigation/mesh/build/navmesh_build_topology.d.ts.map +1 -1
  81. package/src/engine/navigation/mesh/build/navmesh_build_topology.js +20 -7
  82. package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts +15 -0
  83. package/src/engine/navigation/mesh/bvh_query_nearest_face.d.ts.map +1 -0
  84. package/src/engine/navigation/mesh/bvh_query_nearest_face.js +131 -0
  85. package/src/engine/physics/gjk/gjk.d.ts +16 -0
  86. package/src/engine/physics/gjk/gjk.d.ts.map +1 -0
  87. package/src/engine/physics/gjk/gjk.js +378 -0
  88. package/src/core/geom/3d/topology/simplify/quadratic/Quadratic3.d.ts.map +0 -1
  89. 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"}