1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! Intra-mesh vertex weld + index dedup, applied at the mesh SOURCE.
//!
//! The faceted-brep mesher emits geometry per `IfcFace` with no cross-face
//! vertex sharing, so a closed shell duplicates every shared corner once per
//! incident face (~3-6x). That is the direct cause of the ~8x-larger GLBs the
//! reference-extractor comparison flagged on structural (faceted-brep-heavy)
//! models, and it inflates every downstream mesh (render, export, analysis).
//! This weld collapses vertices that share an identical f32 position AND a
//! coinciding (quantized) normal into one, then remaps indices.
//!
//! It runs once, at the single per-element mesh funnel `build_mesh_data`
//! (`ifc_lite_processing::element`), so every element — voided or not, faceted
//! brep or swept solid — arrives welded in its `MeshData`. Because it keys on
//! the quantized normal, coincident positions carrying DISTINCT normals (a
//! crease / cube corner) stay split, so flat shading is preserved (a cube keeps
//! its 24 vertices). World triangles and the world AABB are preserved exactly
//! (welded vertices sit at identical positions; triangle count and winding are
//! unchanged).
//!
//! ## Per-vertex attributes
//!
//! `MeshData`'s only per-vertex-parallel arrays are `positions`, `normals`, and
//! (for textured meshes, #961) `uvs`. The weld carries the UVs through the same
//! remap so they stay 1:1 with the welded positions, AND folds the (quantized)
//! UV into the merge key: two vertices at the same position + normal but
//! DIFFERENT UVs are a legitimate texture SEAM and must stay split, or the
//! texture mapping tears. An untextured mesh (`uvs == None`) contributes a
//! constant `(0, 0)` UV, so its key is effectively position + normal and it gets
//! the full weld benefit (steel faceted breps unaffected).
//!
//! Deterministic and cross-arch (native == wasm32): first-seen order over the
//! original vertex array, integer keys (f32 position bits + a quantized normal +
//! a quantized UV), no float comparison, FMA-free.
use FxHashMap;
use RefCell;
/// Normal quantization grid: components are multiplied by this and rounded to an
/// integer before keying. The shared grid also used by [`crate::facet_weld`]'s
/// `NORMAL_QUANT` and the `consolidate_coplanar` grid, so the weld merges
/// exactly the f32-jittered coplanar normals while keeping any real crease
/// (normals that differ by more than ~1e-3 in a component) split.
use crateNORMAL_QUANT_F32 as NORMAL_QUANT;
/// UV quantization grid (~0.001 texel-fraction resolution). Coarse enough to
/// merge f32 UV jitter on a shared corner, far finer than any real texture seam
/// (a seam jumps the UV by a large fraction of the atlas), so seams stay split.
const UV_QUANT: f32 = 1.0e3;
/// Vertex identity key: exact position bits + quantized normal + quantized UV.
type VKey = ;
/// Per-worker reusable scratch for [`weld_indexed`]'s INTERNAL buffers, cleared
/// (never freed) between meshes. The output buffers (`out_pos`/`out_nrm`/…) still
/// allocate (they escape to the caller); only the transient `map`/`remap`/
/// `first_vert` — allocated and dropped per element by the pre-pool code — are
/// pooled. BYTE-IDENTICAL: `map` is only `.get()`/`.insert()`-ed, never iterated
/// (so its bucket count / residual capacity can't reach the output); `remap` is
/// fully overwritten; `first_vert` is refilled by push in first-seen order and
/// iterated in push order. A cleared, reused buffer replays the identical fill.
thread_local!
/// Weld `positions`/`normals` (3 floats per vertex, equal length), optional
/// `uvs` (2 floats per vertex), and remap `indices`.
///
/// Returns `Some((positions, normals, uvs, indices))` ONLY when at least two
/// vertices actually merged; `uvs` is `Some` iff the input `uvs` was, always
/// 1:1 with the welded positions. Returns `None` when nothing changes — a mesh
/// that is already welded / all-crease (a swept solid, an indexed mesher, a
/// flat-shaded cube), OR a malformed input (normals not matching positions,
/// empty, a UV array not 2-per-vertex, or an out-of-range index). In every
/// `None` case the identity remap would reproduce the input byte-for-byte, so
/// the caller keeps its ORIGINAL buffers and skips the copy: no per-element
/// reallocation on the (common) already-welded path, and a malformed input
/// stays invalid-but-present rather than panicking or being re-associated.
///
/// Because the decision is purely "did any key collide", the funnel stays
/// uniform — no per-geometry-type branching. The weld is idempotent: welding a
/// welded mesh returns `None`.