@@ -11,6 +11,159 @@ import {
1111 getPotentialObstacles ,
1212} from '../../../utils/spatialIndex.js' ;
1313
14+ function repairConnectivity (
15+ result ,
16+ spatialIndex ,
17+ obstacles ,
18+ clearance ,
19+ width ,
20+ height ,
21+ ) {
22+ const nodes = result . nodes || [ ] ;
23+ const edges = result . edges || [ ] ;
24+ if ( ! nodes . length || ! edges . length ) return ;
25+
26+ const idToIndex = new Map ( ) ;
27+ const adj = [ ] ;
28+ for ( let i = 0 ; i < nodes . length ; i ++ ) {
29+ idToIndex . set ( nodes [ i ] . id , i ) ;
30+ adj . push ( [ ] ) ;
31+ }
32+
33+ let edgeLenSum = 0 ;
34+ let edgeLenCount = 0 ;
35+ for ( let i = 0 ; i < edges . length ; i ++ ) {
36+ const e = edges [ i ] ;
37+ const a = idToIndex . get ( e . from ) ;
38+ const b = idToIndex . get ( e . to ) ;
39+ if ( typeof a !== 'number' || typeof b !== 'number' || a === b ) continue ;
40+ adj [ a ] . push ( b ) ;
41+ adj [ b ] . push ( a ) ;
42+
43+ const na = nodes [ a ] ;
44+ const nb = nodes [ b ] ;
45+ const dx = na . x - nb . x ;
46+ const dy = na . y - nb . y ;
47+ edgeLenSum += Math . sqrt ( dx * dx + dy * dy ) ;
48+ edgeLenCount += 1 ;
49+ }
50+
51+ const visited = new Array ( nodes . length ) . fill ( false ) ;
52+ const components = [ ] ;
53+ for ( let i = 0 ; i < nodes . length ; i ++ ) {
54+ if ( visited [ i ] ) continue ;
55+ const stack = [ i ] ;
56+ visited [ i ] = true ;
57+ const comp = [ ] ;
58+ while ( stack . length ) {
59+ const idx = stack . pop ( ) ;
60+ comp . push ( idx ) ;
61+ const list = adj [ idx ] ;
62+ for ( let j = 0 ; j < list . length ; j ++ ) {
63+ const nb = list [ j ] ;
64+ if ( ! visited [ nb ] ) {
65+ visited [ nb ] = true ;
66+ stack . push ( nb ) ;
67+ }
68+ }
69+ }
70+ components . push ( comp ) ;
71+ }
72+
73+ if ( components . length <= 1 ) return ;
74+
75+ const baseLen = edgeLenCount > 0 ? edgeLenSum / edgeLenCount : 0 ;
76+ const diag = Math . sqrt ( width * width + height * height ) ;
77+ const maxBridgeLen = baseLen > 0 ? baseLen * 4 : diag * 0.5 ;
78+
79+ components . sort ( ( a , b ) => b . length - a . length ) ;
80+ const mainSet = new Set ( components [ 0 ] ) ;
81+ const mainIndices = components [ 0 ] . slice ( ) ;
82+
83+ const getPool = ( ax , ay , bx , by ) => {
84+ if ( ! spatialIndex ) return obstacles ;
85+ const poolA = getObstaclesAlongLineDDA ( spatialIndex , ax , ay , bx , by ) || [ ] ;
86+ const poolB = getPotentialObstacles ( spatialIndex , ax , ay , bx , by ) || [ ] ;
87+ if ( ! poolA . length && ! poolB . length ) return obstacles ;
88+ const seen = new Set ( ) ;
89+ const pool = [ ] ;
90+ for ( let i = 0 ; i < poolA . length ; i ++ ) {
91+ const ob = poolA [ i ] ;
92+ const id = ob . id != null ? ob . id : ob ;
93+ if ( ! seen . has ( id ) ) {
94+ seen . add ( id ) ;
95+ pool . push ( ob ) ;
96+ }
97+ }
98+ for ( let i = 0 ; i < poolB . length ; i ++ ) {
99+ const ob = poolB [ i ] ;
100+ const id = ob . id != null ? ob . id : ob ;
101+ if ( ! seen . has ( id ) ) {
102+ seen . add ( id ) ;
103+ pool . push ( ob ) ;
104+ }
105+ }
106+ return pool . length ? pool : obstacles ;
107+ } ;
108+
109+ for ( let ci = 1 ; ci < components . length ; ci ++ ) {
110+ const comp = components [ ci ] ;
111+ let bestU = - 1 ;
112+ let bestV = - 1 ;
113+ let bestDist = Infinity ;
114+
115+ for ( let i = 0 ; i < comp . length ; i ++ ) {
116+ const ui = comp [ i ] ;
117+ const u = nodes [ ui ] ;
118+ for ( let m = 0 ; m < mainIndices . length ; m ++ ) {
119+ const vi = mainIndices [ m ] ;
120+ const v = nodes [ vi ] ;
121+ const dx = u . x - v . x ;
122+ const dy = u . y - v . y ;
123+ const dist = Math . sqrt ( dx * dx + dy * dy ) ;
124+ if ( dist >= bestDist || dist > maxBridgeLen ) continue ;
125+
126+ const pool = getPool ( u . x , u . y , v . x , v . y ) ;
127+ let blocked = false ;
128+ for ( let k = 0 ; k < pool . length ; k ++ ) {
129+ const ob = pool [ k ] ;
130+ if (
131+ lineIntersectsObstacleWithTurf ( u . x , u . y , v . x , v . y , ob , clearance )
132+ ) {
133+ blocked = true ;
134+ break ;
135+ }
136+ }
137+ if ( blocked ) continue ;
138+
139+ bestDist = dist ;
140+ bestU = ui ;
141+ bestV = vi ;
142+ }
143+ }
144+
145+ if ( bestU >= 0 && bestV >= 0 ) {
146+ const fromNode = nodes [ bestU ] ;
147+ const toNode = nodes [ bestV ] ;
148+ const cost = bestDist ;
149+ result . edges . push ( { from : fromNode . id , to : toNode . id , cost } ) ;
150+ adj [ bestU ] . push ( bestV ) ;
151+ adj [ bestV ] . push ( bestU ) ;
152+ if ( ! mainSet . has ( bestU ) ) {
153+ mainSet . add ( bestU ) ;
154+ mainIndices . push ( bestU ) ;
155+ }
156+ for ( let i = 0 ; i < comp . length ; i ++ ) {
157+ const idx = comp [ i ] ;
158+ if ( ! mainSet . has ( idx ) ) {
159+ mainSet . add ( idx ) ;
160+ mainIndices . push ( idx ) ;
161+ }
162+ }
163+ }
164+ }
165+ }
166+
14167/**
15168 * 过滤障碍内部节点
16169 * @param {Array } nodes - 节点数组
@@ -205,6 +358,15 @@ export function sanitizeLayer(result, width, height, obstacles, options = {}) {
205358 ` [步骤3] 清理未引用节点: ${ beforeFinalNodes } 个 → ${ result . nodes . length } 个 (❌移除 ${ removedUnrefNodes } 个)` ,
206359 ) ;
207360
361+ repairConnectivity (
362+ result ,
363+ spatialIndex ,
364+ obstacles ,
365+ clearance ,
366+ width ,
367+ height ,
368+ ) ;
369+
208370 console . log (
209371 `✅ [LayerSanitizer] 完成!最终结果: ${ result . nodes . length } 个节点, ${ result . edges . length } 条边` ,
210372 ) ;
0 commit comments