PageSourceSearch

https://astroknot.netlify.app/modules/VisualComponents/LineManager.js

js astroknot.netlify.app collected 2026-10-03 10:30:05 UTC 7,180 bytes, 193 lines download raw bytes

1// ============================================================
2//  模块6 子模块:连线管理 (增删改 + 可视化控制)
3// ============================================================
4import * as THREE from 'three';
5import { appState } from '../module0_AppState.js';
6import { SpiralFlowLine, PolylineFlowLine } from './FlowLines.js';
7
8// 2D 坐标转 3D 坐标(与 Resize.js arrange3DWith2DLayout 保持一致的比例)
9function to3DFrom2D(x2d, y2d, baseY) {
10  return new THREE.Vector3(x2d * 0.005, baseY, y2d * 0.015);
11}
12
13// ==================== 增量添加单条树连线 ====================
14export function addSingleTreeLine(parentId, childId) {
15  if (!parentId || parentId === appState.VIRTUAL_ROOT_ID) return;
16  if (!appState.positions.has(parentId) || !appState.positions.has(childId)) return;
17  const exists = appState.lineItems.some(it => it.startId === parentId && it.endId === childId && it.edgeType === 'tree');
18  if (exists) return;
19  let l = new SpiralFlowLine(
20    appState.positions.get(parentId),
21    appState.positions.get(childId),
22    Math.random(),
23    {
24      startId: parentId,
25      endId: childId,
26      edgeType: 'tree',
27      parentId: parentId,
28      label: '',
29      labelHidden: true,
30      customColor: null
31    }
32  );
33  appState.scene.add(l.mesh);
34  appState.lineItems.push({ line: l, startId: parentId, endId: childId, edgeType: 'tree' });
35}
36
37// ==================== 增量移除连线 ====================
38export function removeLinesForNodes(nodeIds) {
39  const idSet = new Set(nodeIds);
40  for (let i = appState.lineItems.length - 1; i >= 0; i--) {
41    const it = appState.lineItems[i];
42    if (idSet.has(it.startId) || idSet.has(it.endId)) {
43      it.line.dispose();
44      appState.lineItems.splice(i, 1);
45    }
46  }
47}
48
49// ==================== 重建所有连线 ====================
50export function rebuildAllLines() {
51  // 1. 收集现有树连线的标签及颜色信息
52  const treeLabelMap = new Map();
53  for (let it of appState.lineItems) {
54    const ud = it.line.mesh.userData;
55    if (ud.edgeType === 'tree') {
56      const key = `${ud.startId}->${ud.endId}`;
57      treeLabelMap.set(key, {
58        label: ud.label || '',
59        labelHidden: ud.labelHidden !== undefined ? ud.labelHidden : true,
60        customColor: it.line.customColor ? '#' + it.line.customColor.getHexString() : null
61      });
62    }
63  }
64
65  // 2. 销毁所有旧连线
66  for (let it of appState.lineItems) it.line.dispose();
67  appState.lineItems = [];
68
69  // 3. 生成树连线(带标签恢复)
70  function addTree(node, pid) {
71    if (pid && pid !== appState.VIRTUAL_ROOT_ID && appState.positions.has(pid) && appState.positions.has(node.id)) {
72      const key = `${pid}->${node.id}`;
73      const saved = treeLabelMap.get(key) || { label: '', labelHidden: true, customColor: null };
74      let l = new SpiralFlowLine(
75        appState.positions.get(pid),
76        appState.positions.get(node.id),
77        Math.random(),
78        {
79          startId: pid,
80          endId: node.id,
81          edgeType: 'tree',
82          parentId: pid,
83          label: saved.label,
84          labelHidden: saved.labelHidden,
85          customColor: saved.customColor ? new THREE.Color(saved.customColor) : null
86        }
87      );
88      appState.scene.add(l.mesh);
89      appState.lineItems.push({ line: l, startId: pid, endId: node.id, edgeType: 'tree' });
90    }
91    if (node.children) node.children.forEach(c => addTree(c, node.id));
92  }
93  addTree(appState.methodsTree, null);
94
95  // 4. 交叉连线
96  const edgeCountMap = new Map();
97  const getEdgeKey = (s, t) => [s, t].sort().join('->');
98  appState.crossEdges.forEach(e => {
99    const key = getEdgeKey(e.source, e.target);
100    edgeCountMap.set(key, (edgeCountMap.get(key) || 0) + 1);
101  });
102  const edgeIndexMap = new Map();
103
104  for (let e of appState.crossEdges) {
105    if (appState.positions.has(e.source) && appState.positions.has(e.target)) {
106      const key = getEdgeKey(e.source, e.target);
107      if (!edgeIndexMap.has(key)) edgeIndexMap.set(key, 0);
108      const flowIndex = edgeIndexMap.get(key);
109      edgeIndexMap.set(key, flowIndex + 1);
110
111      if (e.waypoints && e.waypoints.length > 0) {
112        const srcPos3D = appState.positions.get(e.source);
113        const tgtPos3D = appState.positions.get(e.target);
114        const baseY = srcPos3D.y;
115
116        const points3D = [srcPos3D.clone()];
117        for (const wp of e.waypoints) {
118          points3D.push(to3DFrom2D(wp.x, wp.y, baseY));
119        }
120        points3D.push(tgtPos3D.clone());
121
122        let pl = new PolylineFlowLine(points3D, Math.random(), {
123          startId: e.source,
124          endId: e.target,
125          edgeType: 'cross',
126          label: e.label || '',
127          labelHidden: e.labelHidden !== undefined ? e.labelHidden : true,
128          customColor: e.customColor || null
129        });
130        appState.scene.add(pl.mesh);
131        appState.lineItems.push({ line: pl, startId: e.source, endId: e.target, edgeType: 'cross', isPolyline: true });
132        continue;
133      }
134
135      let l = new SpiralFlowLine(
136        appState.positions.get(e.source),
137        appState.positions.get(e.target),
138        Math.random(),
139        {
140          startId: e.source,
141          endId: e.target,
142          edgeType: 'cross',
143          label: e.label || '',
144          labelHidden: e.labelHidden !== undefined ? e.labelHidden : true,
145          customColor: e.customColor || null,
146          flowIndex: flowIndex
147        }
148      );
149      appState.scene.add(l.mesh);
150      appState.lineItems.push({ line: l, startId: e.source, endId: e.target, edgeType: 'cross' });
151    }
152  }
153  updateLinesVis();
154}
155
156// ==================== 拖拽时增量更新连线 ====================
157export function updateLinesForNodes(movedNodeIds) {
158  const idSet = new Set(movedNodeIds);
159  for (let it of appState.lineItems) {
160    if (idSet.has(it.startId) || idSet.has(it.endId)) {
161      const startPos = appState.positions.get(it.startId);
162      const endPos = appState.positions.get(it.endId);
163      if (!startPos || !endPos) continue;
164
165      if (it.isPolyline && it.line instanceof PolylineFlowLine) {
166        const edgeData = appState.crossEdges.find(e =>
167          e.source === it.startId && e.target === it.endId &&
168          e.waypoints && e.waypoints.length > 0
169        );
170        const baseY = startPos.y;
171        const points3D = [startPos.clone()];
172        if (edgeData?.waypoints) {
173          for (const wp of edgeData.waypoints) {
174            points3D.push(to3DFrom2D(wp.x, wp.y, baseY));
175          }
176        }
177        points3D.push(endPos.clone());
178        it.line.updatePositions(points3D);
179      } else {
180        it.line.updatePositions(startPos, endPos);
181      }
182    }
183  }
184}
185
186// ==================== 连线可见性控制 ====================
187export function updateLinesVis() {
188  for (let it of appState.lineItems) {
189    let sv = appState.nodeMeshes.get(it.startId)?.visible ?? false;
190    let ev = appState.nodeMeshes.get(it.endId)?.visible ?? false;
191    it.line.setVisible(sv && ev);
192  }
193}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.