Skip to content

Commit 9149484

Browse files
committed
feat: add connectivity repair to layer sanitizer with automatic bridge edge generation
- Implemented repairConnectivity function to detect and repair disconnected graph components - Added component detection using depth-first search to identify isolated node clusters - Implemented automatic bridge edge generation between disconnected components and main graph - Added distance-based bridge selection with configurable max length (4x average edge length or 0.5x diagonal) - Integrated spatial index optimization
1 parent 369ba9d commit 9149484

1 file changed

Lines changed: 162 additions & 0 deletions

File tree

src/core/workers/layer/layerSanitizer.js

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)