@@ -33,38 +33,11 @@ use crate::Mesh;
3333use rustc_hash:: FxHashMap ;
3434use std:: cell:: RefCell ;
3535
36- /// Incident-triangle record for one undirected welded edge. A boundary edge has
37- /// one incident triangle and a manifold edge exactly two, so the two triangle
38- /// slots are stored INLINE — replacing the old per-edge heap `Vec<usize>`, which
39- /// allocated ~1.5 tiny Vecs per triangle and dominated the allocator churn of
40- /// this pass on mesh-heavy models. `count` is the TRUE incidence; a value > 2
41- /// marks a non-manifold edge, which the propagation skips before ever reading
42- /// the slots. Only the first two triangles are consulted, stored in ascending
43- /// scan order (identical to the old `Vec` push order), so the BFS traversal —
44- /// and therefore every flip decision — is byte-identical.
45- #[ derive( Clone , Copy , Default ) ]
46- struct EdgeInc {
47- tris : [ usize ; 2 ] ,
48- count : u32 ,
49- }
50-
51- impl EdgeInc {
52- #[ inline]
53- fn push ( & mut self , t : usize ) {
54- if ( self . count as usize ) < 2 {
55- self . tris [ self . count as usize ] = t;
56- }
57- self . count += 1 ;
58- }
59-
60- /// The incident triangles the propagation may consult (the first two, in
61- /// push order). Only reached for `count` of 1 or 2 — the `count > 2` path
62- /// `continue`s first — so this yields exactly what the old `Vec` iterated.
63- #[ inline]
64- fn incident ( & self ) -> & [ usize ] {
65- & self . tris [ ..( self . count as usize ) . min ( 2 ) ]
66- }
67- }
36+ #[ path = "mesh_orient_adjacency.rs" ]
37+ mod adjacency;
38+ use adjacency:: EdgeAdjacency ;
39+ #[ cfg( test) ]
40+ use adjacency:: EdgeInc ;
6841
6942/// Vertex weld grid scale (reciprocal of a 10 µm grid, i.e. positions are
7043/// quantized to `round(v * WELD_SCALE)`): fine enough not to merge distinct
@@ -76,7 +49,7 @@ const WELD_SCALE: f64 = 1.0e5;
7649
7750/// Per-worker reusable scratch for [`orient_mesh_outward`], cleared (never freed)
7851/// between meshes. The pass runs once per assembled submesh (~109k times on a
79- /// mesh-heavy model), so each fresh call's two `FxHashMap`s + six `Vec`s were
52+ /// mesh-heavy model), so each fresh call's two `FxHashMap`s + the traversal `Vec`s were
8053/// ~4-6% of busy CPU on pure-brep/steel models; pooling makes it allocate-once,
8154/// clear-many. BYTE-IDENTICAL: neither map is ever iterated — both are only
8255/// `.entry()`-inserted (order fixed by the deterministic scan) and keyed-looked-up,
@@ -88,7 +61,7 @@ struct OrientScratch {
8861 vid_of : FxHashMap < ( i64 , i64 , i64 ) , u32 > ,
8962 vpos : Vec < [ f64 ; 3 ] > ,
9063 corner : Vec < u32 > ,
91- edge_tris : FxHashMap < ( u32 , u32 ) , EdgeInc > ,
64+ edge_tris : EdgeAdjacency ,
9265 flip : Vec < bool > ,
9366 visited : Vec < bool > ,
9467 comp : Vec < usize > ,
@@ -221,9 +194,14 @@ pub fn orient_mesh_outward_verdict(mesh: &mut Mesh) -> OrientVerdict {
221194 corner. clear ( ) ;
222195 corner. reserve ( mesh. indices . len ( ) ) ;
223196
224- // Weld positions -> welded vertex id; record the welded vid of every corner.
197+ // #3988: indexed corners repeatedly use the SAME source vertex. Reuse its
198+ // welded id, keeping the original first-corner insertion order. Sparse meshes
199+ // use corner slots instead, so scratch never exceeds the old corner budget.
200+ let indexed = vertex_count < mesh. indices . len ( ) ;
201+ if indexed { corner. resize ( vertex_count, u32:: MAX ) ; }
225202 let q = |v : f32 | ( v as f64 * WELD_SCALE ) . round ( ) as i64 ;
226203 for & idx in & mesh. indices {
204+ if indexed && corner[ idx as usize ] != u32:: MAX { continue ; }
227205 let b = idx as usize * 3 ;
228206 let key = (
229207 q ( mesh. positions [ b] ) ,
@@ -239,22 +217,23 @@ pub fn orient_mesh_outward_verdict(mesh: &mut Mesh) -> OrientVerdict {
239217 ] ) ;
240218 id
241219 } ) ;
242- corner. push ( vid) ;
220+ if indexed { corner[ idx as usize ] = vid ; } else { corner . push ( vid) ; }
243221 }
244- let tv = |t : usize | [ corner[ 3 * t] , corner[ 3 * t + 1 ] , corner[ 3 * t + 2 ] ] ;
222+ let tv = |t : usize | std:: array:: from_fn :: < _ , 3 , _ > ( |k| {
223+ corner[ if indexed { mesh. indices [ 3 * t + k] as usize } else { 3 * t + k } ]
224+ } ) ;
245225
246226 // Undirected welded edge -> incident triangles. >2 incident ⇒ non-manifold.
247227 // A closed manifold has ~1.5 edges per triangle; reserve to skip rehashing.
248- edge_tris. clear ( ) ;
249- edge_tris. reserve ( ntri * 2 ) ;
228+ edge_tris. reset ( ntri) ;
250229 for t in 0 ..ntri {
251230 let v = tv ( t) ;
252- for & ( a, b) in & [ ( v[ 0 ] , v[ 1 ] ) , ( v[ 1 ] , v[ 2 ] ) , ( v[ 2 ] , v[ 0 ] ) ] {
231+ for ( slot , & ( a, b) ) in [ ( v[ 0 ] , v[ 1 ] ) , ( v[ 1 ] , v[ 2 ] ) , ( v[ 2 ] , v[ 0 ] ) ] . iter ( ) . enumerate ( ) {
253232 if a == b {
254233 continue ; // welded-degenerate edge
255234 }
256235 let key = if a < b { ( a, b) } else { ( b, a) } ;
257- edge_tris. entry ( key ) . or_default ( ) . push ( t ) ;
236+ edge_tris. push ( key , t * 3 + slot ) ;
258237 }
259238 }
260239
@@ -303,22 +282,16 @@ pub fn orient_mesh_outward_verdict(mesh: &mut Mesh) -> OrientVerdict {
303282 } else {
304283 [ ( v[ 0 ] , v[ 1 ] ) , ( v[ 1 ] , v[ 2 ] ) , ( v[ 2 ] , v[ 0 ] ) ]
305284 } ;
306- for & ( a, b) in & dirs {
285+ for ( slot , & ( a, b) ) in dirs. iter ( ) . enumerate ( ) {
307286 if a == b {
308287 continue ;
309288 }
310289 let key = if a < b { ( a, b) } else { ( b, a) } ;
311- let inc = & edge_tris[ & key] ;
312- if inc. count != 2 {
313- closed = false ; // boundary (1) or non-manifold (>2) edge
314- }
315- if inc. count > 2 {
316- continue ; // ambiguous — don't propagate across a non-manifold edge
317- }
318- for & nb in inc. incident ( ) {
319- if nb == t {
320- continue ;
321- }
290+ let original_slot = if flip[ t] { 2 - slot } else { slot } ;
291+ let ( closed_edge, neighbors) = edge_tris. neighbors ( key, t * 3 + original_slot) ;
292+ if !closed_edge { closed = false ; }
293+ for nb in neighbors {
294+ if nb == usize:: MAX || nb == t { continue ; }
322295 // A consistent neighbour must traverse this edge as (b, a). Its
323296 // UNFLIPPED winding has (a, b) iff it must flip to do so.
324297 let nv = tv ( nb) ;
0 commit comments