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
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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
// 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 AT LEAST once per element, in the OBJECT frame, at the last moment
//! before the placement is baked in: [`weld_mesh`] from `apply_placement` and
//! [`weld_sub_mesh`] from `apply_submesh_placement`. Every element — voided or
//! not, faceted brep or swept solid, single-item or per-style sub-meshes —
//! therefore arrives welded in its `MeshData`. Some are welded a SECOND time,
//! post-bake, by [`weld`] called from `build_mesh_data`; its doc says which and
//! why. 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). Triangles and the AABB are preserved exactly (welded vertices sit
//! at identical positions; triangle count and winding are unchanged).
//!
//! ## Why the object frame, and not after the bake (#4103)
//!
//! The position part of the key is the raw f32 BIT PATTERN, so what the weld
//! merges depends on the magnitude of the coordinates it is handed. An f32 ULP
//! is ~6e-8 m at 0.5 m and ~2e-6 m at 30 m, so welding baked world coordinates
//! silently applies an epsilon that grows with the element's distance from the
//! origin, and welding the same source geometry at two different placements
//! merges two different sets of vertices.
//!
//! That broke a contract the pipeline depends on. Every occurrence of one
//! `IfcRepresentationMap` is a clone of ONE cached source mesh
//! (`router::mapped_item`), and every direct-solid `rep_identity` is a hash of
//! the mesh BEFORE placement (`router::processing::direct_rep_identity`), so
//! occurrences of one representation are meant to be bit-identical. A post-bake
//! weld rewrote each of them differently, and `instancing::collate_refs` refuses
//! a group whose members disagree on vertex count, so nothing ever collated:
//! ten armchairs sharing one `IfcRepresentationMap` shipped as ten full meshes
//! with vertex counts spread over 0.9%, and a cached Parquet artifact came out
//! three times the size of its own IFC.
//!
//! In the object frame the key no longer depends on the element's PLACEMENT, so
//! occurrences that differ only by where they were put weld identically.
//! Placement is rigid, so nothing that merges here would have failed to merge
//! after the bake for a geometric reason; only the accidental collisions go
//! away, and those were never intended.
//!
//! ## What this does NOT fix, measured
//!
//! Three things survive, all narrower than the bug above but none zero.
//!
//! A per-occurrence `IfcMappedItem` MappingTarget is baked in by
//! `router::mapped_item` BEFORE the mesh reaches `apply_placement`, so the weld
//! still sees target-transformed coordinates. Occurrences of one
//! `IfcRepresentationMap` whose targets differ by enough to move the f32
//! exponent still weld to different vertex counts, and `collate_refs` still
//! refuses that group. Measured on the `issue_4103_shared_map_buffer_identity`
//! fixture with the offsets moved from the placement into the target: 8/8/8
//! vertices when the placement varies (fixed), 8/4/4 when the target varies
//! (unfixed). Closing that means welding the cached source once, before the
//! target bake, and suppressing this weld for a mesh already welded — a
//! different change.
//!
//! The invariant this weld establishes — no two vertices sharing a (position
//! bits, quantized normal) key — holds in the frame the weld RAN in, not
//! necessarily in the world buffer that ships. A rigid placement can map two
//! vertices that are distinct in the object frame onto one f32 world position;
//! main merged those post-bake and this deliberately does not, because doing so
//! is exactly the placement-dependence being removed. Measured across the six
//! ara3d models: 104 of 15,211 shipped meshes carry at least one such duplicate
//! key, 494 of 1,093,616 vertices (0.68% and 0.045%); AC20-FZK-Haus has none.
//! A post-bake weld to remove them would reintroduce #4103.
//!
//! Welding is not the only stage that can make two occurrences disagree. Anything
//! that edits INDICES after the bake can too, and two do: `degenerate::clean`
//! compares a triangle height computed from f32 world positions against an
//! ABSOLUTE threshold, and `mesh_orient`'s adjacency grid is 10 um, which is
//! finer than the f32 world grid at 128 m and beyond (one ULP there is 1.5e-5 m).
//! Either can drop or reorient a different triangle in one occurrence than in
//! another, and `instancing::group` rejects a group whose members' index buffers
//! differ, which is the #4103 symptom reached by another route. UNMEASURED: this
//! was reasoned from the thresholds, not observed, and the fixture here uses
//! half-unit squares that cannot exercise it. Recorded so the next person
//! investigating a stubborn collation rejection does not assume the weld is the
//! only candidate.
//!
//! ## 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`.
/// Weld a mesh's vertices in place, carrying `uvs` through the same remap and
/// returning them still 1:1 with the welded positions.
///
/// The one body behind every entry point here. It welds in whatever frame the
/// CALLER hands it, which is the decision the caller owns: see the module doc
/// for why the object frame is the right one for shared geometry, and what a
/// world-frame weld does to it.
///
/// Computes the normals first when a processor left them absent or short. The
/// key carries the quantized normal so a crease stays split, and `weld_indexed`
/// REFUSES a mesh whose normals do not match its positions 1:1 while signalling
/// that refusal with the same `None` it returns for "nothing collided" — so
/// without this, a silently skipped weld is indistinguishable from an
/// already-welded one, on every model. `calculate_normals` accumulates from the
/// triangle winding, so it needs only positions and indices and is happy in any
/// frame; `transform_mesh_world` then rotates the result into world space with
/// the positions. `build_mesh_data`'s call already guarantees 1:1 normals; the
/// two placement appliers do NOT, which is where this earns its keep.
///
/// `None` from `weld_indexed` leaves the mesh and the UVs untouched, with no
/// reallocation.
///
/// ## What legitimately calls this AFTER the bake
///
/// `element::build_mesh_data`, for geometry with no cross-occurrence identity to
/// protect (`instance_meta` absent). Two populations arrive there, and they are
/// not the same:
///
/// - Geometry BORN after the placement bake, which no earlier weld could have
/// reached: CSG void-cut output, layer slices, the #858 palette-split parts.
/// This is the weld that collapses the kernel's per-face output for them.
/// - Geometry already welded in the object frame that merely lost its
/// `instance_meta` on the way there — a void host (welded in `apply_placement`,
/// then cut, then nulled at `voids::process_element_with_voids`), a multi-item
/// element (`processing.rs` keeps the metadata only for a single instanceable
/// item), a textured face-set sub-mesh. For these it is a SECOND weld: cheap
/// when nothing new collides, but it CAN merge world-frame coincidences the
/// object frame kept apart, so their shipped buffers are placement-dependent in
/// the way the module doc describes. Accepted because none of them is shared.
///
/// Shared geometry must not reach that call site. `build_mesh_data` keeps it out
/// on `instance_meta`; #4122 is about recording the answer instead of inferring
/// it.
/// Weld a `Mesh`'s source vertices in place, from `apply_placement`, where the
/// vertices are still in the object frame. See the module doc for why that is
/// the frame that matters.
///
/// Stamps `welded_in_object_frame` regardless of whether `weld` actually
/// merged anything: "already welded" and "nothing collided" both leave the
/// buffers valid for `build_mesh_data`'s guard (#4122), and `weld_indexed`'s
/// `None` is exactly the "no merge needed" signal, not a refusal to run.
pub
/// [`weld_mesh`] for a `SubMesh`, carrying its UVs through the same remap so they
/// stay 1:1 with the welded positions. The quantized UV is part of the key, so a
/// texture seam's coincident corners stay split (#961).
pub