Module mesh_weld

Module mesh_weld 

Source
Expand description

Intra-mesh vertex weld + index dedup applied at the per-element mesh source (build_mesh_data), collapsing the faceted-brep per-face vertex duplication while keeping creases (distinct normals) split. 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.

Functions§

weld
Weld a mesh’s vertices in place, carrying uvs through the same remap and returning them still 1:1 with the welded positions.
weld_indexed
Weld positions/normals (3 floats per vertex, equal length), optional uvs (2 floats per vertex), and remap indices.