@@ -60,7 +60,8 @@ interface PolyState {
6060interface EdgeOwners {
6161 a : Vec3 ;
6262 b : Vec3 ;
63- owners : number [ ] ;
63+ first : number ;
64+ second : number ;
6465}
6566
6667const sub = ( a : Vec3 , b : Vec3 ) : Vec3 => [ a [ 0 ] - b [ 0 ] , a [ 1 ] - b [ 1 ] , a [ 2 ] - b [ 2 ] ] ;
@@ -303,6 +304,28 @@ function rotateToNonCollinearStart(vertices: Vec3[], uvs?: Vec2[]): { vertices:
303304export function mergePolygons ( input : Polygon [ ] ) : Polygon [ ] {
304305 const out : Polygon [ ] = [ ] ;
305306 const polys : PolyState [ ] = [ ] ;
307+ const vertexKeyCache = new WeakMap < Vec3 , string > ( ) ;
308+ const cachedVertexKey = ( vertex : Vec3 ) : string => {
309+ const current = vertexKeyCache . get ( vertex ) ;
310+ if ( current ) return current ;
311+ const key = vertexKey ( vertex ) ;
312+ vertexKeyCache . set ( vertex , key ) ;
313+ return key ;
314+ } ;
315+ const cachedEdgeKey = ( a : Vec3 , b : Vec3 ) : string => {
316+ const ka = cachedVertexKey ( a ) ;
317+ const kb = cachedVertexKey ( b ) ;
318+ return ka < kb ? `${ ka } |${ kb } ` : `${ kb } |${ ka } ` ;
319+ } ;
320+ const cachedDirectedEdgeKey = ( a : Vec3 , b : Vec3 ) : string =>
321+ `${ cachedVertexKey ( a ) } >${ cachedVertexKey ( b ) } ` ;
322+ const cachedDirectedEdgeSet = ( vertices : Vec3 [ ] ) : Set < string > => {
323+ const edges = new Set < string > ( ) ;
324+ for ( let k = 0 ; k < vertices . length ; k ++ ) {
325+ edges . add ( cachedDirectedEdgeKey ( vertices [ k ] , vertices [ ( k + 1 ) % vertices . length ] ) ) ;
326+ }
327+ return edges ;
328+ } ;
306329 let workUnits = 0 ;
307330 let workBudgetExhausted = false ;
308331 const consumeWork = ( units : number ) : boolean => {
@@ -319,7 +342,7 @@ export function mergePolygons(input: Polygon[]): Polygon[] {
319342 if ( polygon ) out . push ( polygon ) ;
320343 continue ;
321344 }
322- const verts = polygon . vertices . map ( ( v ) => [ v [ 0 ] , v [ 1 ] , v [ 2 ] ] as Vec3 ) ;
345+ const verts = polygon . vertices ;
323346 const plane = planeOf ( verts ) ;
324347 if ( ! plane ) {
325348 out . push ( polygon ) ;
@@ -345,7 +368,7 @@ export function mergePolygons(input: Polygon[]): Polygon[] {
345368 textureTriangles,
346369 normal : plane . normal ,
347370 d : plane . d ,
348- directedEdges : directedEdgeSet ( verts ) ,
371+ directedEdges : cachedDirectedEdgeSet ( verts ) ,
349372 alive : true ,
350373 data : polygon . data ,
351374 } ) ;
@@ -370,36 +393,35 @@ export function mergePolygons(input: Polygon[]): Polygon[] {
370393 if ( ! p . alive ) continue ;
371394 const n = p . vertices . length ;
372395 if ( ! consumeWork ( n ) ) return false ;
373- p . directedEdges = new Set ( ) ;
374396 for ( let k = 0 ; k < n ; k ++ ) {
375397 const a = p . vertices [ k ] ;
376398 const b = p . vertices [ ( k + 1 ) % n ] ;
377- p . directedEdges . add ( directedEdgeKey ( a , b ) ) ;
378- const key = edgeKey ( a , b ) ;
399+ const key = cachedEdgeKey ( a , b ) ;
379400 let edge = edgeIndex . get ( key ) ;
380401 if ( ! edge ) {
381- edge = { a, b, owners : [ ] } ;
402+ edge = { a, b, first : i , second : - 1 } ;
382403 edgeIndex . set ( key , edge ) ;
404+ } else if ( edge . second < 0 ) {
405+ edge . second = i ;
383406 }
384- edge . owners . push ( i ) ;
385407 }
386408 }
387409
388410 let mergedThisPass = false ;
389411
390412 const edgeDirection = ( poly : PolyState , e0 : Vec3 , e1 : Vec3 ) : 1 | - 1 | 0 => {
391- if ( poly . directedEdges . has ( directedEdgeKey ( e0 , e1 ) ) ) return 1 ;
392- if ( poly . directedEdges . has ( directedEdgeKey ( e1 , e0 ) ) ) return - 1 ;
413+ if ( poly . directedEdges . has ( cachedDirectedEdgeKey ( e0 , e1 ) ) ) return 1 ;
414+ if ( poly . directedEdges . has ( cachedDirectedEdgeKey ( e1 , e0 ) ) ) return - 1 ;
393415 return 0 ;
394416 } ;
395417
396418 for ( const edge of edgeIndex . values ( ) ) {
397- const { owners } = edge ;
398- // owners can have >2 if a degenerate input had three+ polys sharing
399- // an edge; we still try each pair below — but the simple dedupe
400- // skips index entries where both polys were already merged away.
401- if ( owners . length < 2 ) continue ;
402- const [ ai , bi ] = owners ;
419+ // Degenerate inputs can have three+ polys sharing an edge. The old
420+ // array path only tried the first two owners, so the fixed slots keep
421+ // that behavior without allocating for every boundary edge.
422+ if ( edge . second < 0 ) continue ;
423+ const ai = edge . first ;
424+ const bi = edge . second ;
403425 if ( ai === bi ) continue ;
404426 const a = polys [ ai ] ;
405427 const b = polys [ bi ] ;
@@ -433,7 +455,7 @@ export function mergePolygons(input: Polygon[]): Polygon[] {
433455
434456 a . vertices = merged . vertices ;
435457 a . uvs = merged . uvs ;
436- a . directedEdges = directedEdgeSet ( merged . vertices ) ;
458+ a . directedEdges = cachedDirectedEdgeSet ( merged . vertices ) ;
437459 a . textureTriangles = hasTexture
438460 ? [ ...( a . textureTriangles ?? [ ] ) , ...( b . textureTriangles ?? [ ] ) ]
439461 : undefined ;
@@ -455,11 +477,11 @@ export function mergePolygons(input: Polygon[]): Polygon[] {
455477 for ( const p of polys ) {
456478 if ( ! p . alive ) continue ;
457479 const out_p : Polygon = {
458- vertices : p . vertices ,
480+ vertices : p . vertices . map ( ( vertex ) => [ vertex [ 0 ] , vertex [ 1 ] , vertex [ 2 ] ] as Vec3 ) ,
459481 color : p . color ,
460482 } ;
461483 if ( p . texture ) out_p . texture = p . texture ;
462- if ( p . uvs ) out_p . uvs = p . uvs ;
484+ if ( p . uvs ) out_p . uvs = p . uvs . map ( ( uv ) => [ uv [ 0 ] , uv [ 1 ] ] as Vec2 ) ;
463485 if ( p . textureTriangles ?. length ) out_p . textureTriangles = p . textureTriangles ;
464486 if ( p . data ) out_p . data = p . data ;
465487 out . push ( out_p ) ;
0 commit comments