1// ============================================================ 2// UI / Resize.js â çªå£ç¼©æ¾ + èªå¨å¸å±å¼¹åº 3// ============================================================ 4import * as THREE from 'three'; 5import { appState } from '../module0_AppState.js'; 6import { processSidebar2DPanning } from '../richEditor/tree-panel.js'; 7import { layoutTree, assignCoordinates, extractNodePositions } from '../2DView/index.js'; 8import { rebuildAllLines, generateRandomPosition } from '../VisualComponents/index.js'; 9import { saveCurrentProjectData } from '../module2_TreeData.js'; 10import { groupRects } from '../2DView/shared.js'; 11 12export function bindResize() { 13 window.addEventListener('resize', () => { 14 appState.camera.aspect = window.innerWidth / window.innerHeight; 15 appState.camera.updateProjectionMatrix(); 16 appState.renderer.setSize(window.innerWidth, window.innerHeight); 17 appState.effectComposer.setSize(window.innerWidth, window.innerHeight); 18 appState.labelRenderer.setSize(window.innerWidth, window.innerHeight); 19 if (appState.renderer && appState.renderer.domElement) { 20 appState.renderer.domElement.style.width = window.innerWidth + 'px'; 21 appState.renderer.domElement.style.height = window.innerHeight + 'px'; 22 } 23 }); 24 25 // ============================================================ 26// ææå¾å±èªå¨æåï¼2D 模å¼ä¸éåææå¾å±éä¸éæï¼ 27// ============================================================ 28function arrangeAllLayers() { 29 const sortedLayers = [...appState.layers].sort((a, b) => a.order - b.order); 30 const rootNode = appState.methodsTree; 31 if (!rootNode || !rootNode.id) return; 32 33 // ä¿åå½åå¾å± 34 const curLayer = appState.getCurrentLayer(); 35 if (curLayer) { 36 curLayer.positions2D = new Map(appState.positions2D); 37 } 38 39 // 1. å ç¨ç¬¬ä¸ä¸ªå¾å±è·ä¸æ¬¡å¸å±ï¼æ¿å°æ åä½ç½®æ å° 40 let referencePositions = null; 41 const firstLayer = sortedLayers[0]; 42 if (firstLayer) { 43 appState.currentLayerId = firstLayer.id; 44 appState.positions2D = firstLayer.positions2D || new Map(); 45 if (appState.autoArrangeTreeLayout) { 46 appState.autoArrangeTreeLayout(); 47 } 48 // ä¿å第ä¸ä¸ªå¾å±çæåç»æä½ä¸ºåºå 49 referencePositions = new Map(appState.positions2D); 50 firstLayer.positions2D = new Map(appState.positions2D); 51 } 52 53 // 2. å ¶ä½å¾å±ï¼ç¨ç¬¬ä¸ä¸ªå¾å±çæ åä½ç½®è¦çï¼å¹¶å移对é½å°ç¬¬ä¸ä¸ªå¾å±çæ ¹èç¹ä½ç½® 54 // å æ¾å°ç¬¬ä¸ä¸ªå¾å±ç primary nodeï¼ç¬¬ä¸ä¸ªå¯è§èç¹ï¼ä½ä¸ºéç¹ 55 let anchorPos = null; 56 if (firstLayer) { 57 for (const [id, pos] of (referencePositions || [])) { 58 if (id !== rootNode.id && firstLayer.nodeIds && firstLayer.nodeIds.has(id)) { 59 anchorPos = { x: pos.x, y: pos.y }; 60 break; 61 } 62 } 63 } 64 65 for (let i = 1; i < sortedLayers.length; i++) { 66 const layer = sortedLayers[i]; 67 appState.currentLayerId = layer.id; 68 appState.positions2D = layer.positions2D || new Map(); 69 70 // ç¨ç¬¬ä¸ä¸ªå¾å±çæ åä½ç½®è¦çå½åå¾å±çææèç¹ä½ç½® 71 if (referencePositions) { 72 for (const [nodeId, pos] of referencePositions) { 73 appState.positions2D.set(nodeId, { x: pos.x, y: pos.y }); 74 } 75 // 以å½åå¾å±çæ ¹èç¹ä½ç½®ä¸ºåºåéæ°è¿è¡å¸å±ï¼ç¡®ä¿æ ç»æä¸è´ 76 appState.autoArrangeTreeLayout(); 77 78 // åºç¨å移对é½ï¼ä½¿è¯¥å¾å±ç primary node ä¸ç¬¬ä¸ä¸ªå¾å±ç primary node å¨åä¸ä½ç½® 79 // æ ¹èç¹ä½ç½®ä¸åä¸å¯¹é½åç§»ï¼åªç§»å¨å代èç¹ï¼ 80 if (anchorPos && layer.nodeIds) { 81 let layerPrimaryPos = null; 82 for (const [id, pos] of appState.positions2D) { 83 if (id !== rootNode.id && layer.nodeIds.has(id)) { 84 layerPrimaryPos = { x: pos.x, y: pos.y }; 85 break; 86 } 87 } 88 if (layerPrimaryPos) { 89 const offsetX = anchorPos.x - layerPrimaryPos.x; 90 const offsetY = anchorPos.y - layerPrimaryPos.y; 91 if (offsetX !== 0 || offsetY !== 0) { 92 const newPositions = new Map(); 93 for (const [id, pos] of appState.positions2D) { 94 if (id === rootNode.id) { 95 // æ ¹èç¹ä½ç½®ä¿æä¸å 96 newPositions.set(id, { x: pos.x, y: pos.y }); 97 } else { 98 newPositions.set(id, { x: pos.x + offsetX, y: pos.y + offsetY }); 99 } 100 } 101 appState.positions2D = newPositions; 102 } 103 } 104 } 105 } 106 layer.positions2D = new Map(appState.positions2D); 107 } 108 109 // æ¢å¤å½åå¾å± 110 if (curLayer) { 111 appState.currentLayerId = curLayer.id; 112 appState.positions2D = curLayer.positions2D || new Map(); 113 } 114 115 // å·æ° 2D è§å¾ 116 if (appState.refresh2DView) appState.refresh2DView();
117 if (appState.refreshTreePanel) appState.refreshTreePanel(); 118} 119 120// ============================================================ 121// 3D é»è®¤æ£å¸æå 122// ============================================================ 123function arrange3DDefault() { 124 appState.layer3DLayout = false; 125 // éèå¾å±æé® 126 const layerBtn = document.getElementById('layerIconBtn'); 127 if (layerBtn) layerBtn.style.display = 'none'; 128 // æ¸ é¤å¾å±é«äº®ç©å½¢ 129 if (appState.layerHighlights) { 130 appState.layerHighlights.forEach(h => appState.scene.remove(h)); 131 appState.layerHighlights = []; 132 } 133 // æ¸ é¤3Dç»ç¾¤ç©å½¢ 134 if (appState.groupRectMeshes) { 135 appState.groupRectMeshes.forEach(m => appState.scene.remove(m)); 136 appState.groupRectMeshes = []; 137 } 138 139 const existing = []; 140 const base = new THREE.Vector3(0, 0, 0); 141 const nodeIds = Array.from(appState.positions.keys()).filter(id => id !== appState.VIRTUAL_ROOT_ID); 142 for (const id of nodeIds) { 143 const pos = generateRandomPosition(existing, base); 144 appState.positions.set(id, pos); 145 existing.push(pos); 146 const obj = appState.nodeMeshes.get(id); 147 if (obj) { 148 obj.mesh.position.copy(pos); 149 if (obj.label) { 150 obj.label.position.set(pos.x, pos.y + appState.NODE_RADIUS + 0.28, pos.z); 151 } 152 } 153 } 154 rebuildAllLines(); 155 saveCurrentProjectData(); 156} 157 158// ============================================================ 159// 3D æ 2D å¸å±æåï¼ä¸åå¾å±æå±åºåä¸å å ï¼ 160// ============================================================ 161function arrange3DWith2DLayout() { 162 const rootNode = appState.methodsTree; 163 if (!rootNode || !rootNode.id) return; 164 165 appState.layer3DLayout = true; 166 const LAYER_SPACING = 4; // æ¯å±ä¹é´ç Y è½´é´è· 167 appState.layer3DSpacing = LAYER_SPACING; 168 const sortedLayers = [...appState.layers].sort((a, b) => a.order - b.order); 169 170 // ä¿åå½åå¾å±ç 2D ä½ç½® 171 const curLayer = appState.getCurrentLayer(); 172 if (curLayer) { 173 curLayer.positions2D = new Map(appState.positions2D); 174 } 175 176 // 1. å ç¨ç¬¬ä¸ä¸ªå¾å±è·ä¸æ¬¡å¸å±ï¼æ¿å°æ åä½ç½®æ å° 177 let referencePositions = null; 178 const firstLayer = sortedLayers[0]; 179 if (firstLayer) { 180 appState.currentLayerId = firstLayer.id; 181 appState.positions2D = firstLayer.positions2D || new Map(); 182 const rootPos = appState.positions2D.get(rootNode.id) || { x: -500, y: -200 }; 183 184 // ä¿åç宿 ¹èç¹çä½ç½® 185 const realRootPositions = new Map(); 186 for (const child of (rootNode.children || [])) { 187 if (child && child.id) { 188 const saved = appState.positions2D.get(child.id); 189 if (saved) realRootPositions.set(child.id, { x: saved.x, y: saved.y }); 190 } 191 } 192 193 const layout = layoutTree(rootNode); 194 assignCoordinates(layout, 0, 0); 195 const positions = extractNodePositions(layout); 196 // èææ ¹èç¹ + ç宿 ¹èç¹ä½ç½®ä¸åä¸éæ 197 positions.set(rootNode.id, { x: rootPos.x, y: rootPos.y }); 198 appState.positions2D.set(rootNode.id, { x: rootPos.x, y: rootPos.y }); 199 for (const [id, saved] of realRootPositions) { 200 positions.set(id, { x: saved.x, y: saved.y }); 201 appState.positions2D.set(id, { x: saved.x, y: saved.y }); 202 } 203 const rootLayoutPos = positions.get(rootNode.id); 204 if (rootLayoutPos) { 205 const offsetX = rootPos.x - rootLayoutPos.x; 206 const offsetY = rootPos.y - rootLayoutPos.y; 207 for (const [id, pos] of positions) { 208 if (id === rootNode.id || realRootPositions.has(id)) continue; 209 pos.x += offsetX; 210 pos.y += offsetY; 211 } 212 } 213 referencePositions = new Map(positions); 214 } 215 216 // 2. ç¨ç»ä¸çæ åä½ç½®æ å°å°åå¾å±ç 3D åæ 217 if (referencePositions) { 218 // æ¾å°ç¬¬ä¸ä¸ªå¾å±ç primary nodeï¼ç¬¬ä¸ä¸ªéèææ ¹ä¸å¨å¾å±ä¸çèç¹ï¼ä½ä¸ºéç¹ 219 let anchorPos = null; 220 if (firstLayer) { 221 for (const [id, pos] of referencePositions.entries()) { 222 if (id !== rootNode.id && firstLayer.nodeIds && firstLayer.nodeIds.has(id)) { 223 anchorPos = { x: pos.x, y: pos.y }; 224 break; 225 } 226 } 227 } 228
229 sortedLayers.forEach((layer, layerIdx) => { 230 const yBase = layerIdx * LAYER_SPACING; 231 232 // 计ç®å½åå¾å±çåç§»éï¼ä½¿å ¶ primary node 对é½å°ç¬¬ä¸ä¸ªå¾å±çéç¹ä½ç½® 233 let offsetX = 0, offsetY = 0; 234 if (anchorPos && layer.nodeIds) { 235 for (const [id, pos] of referencePositions.entries()) { 236 if (id !== rootNode.id && layer.nodeIds.has(id)) { 237 offsetX = anchorPos.x - pos.x; 238 offsetY = anchorPos.y - pos.y; 239 break; 240 } 241 } 242 } 243 244 for (const [id, pos] of referencePositions.entries()) { 245 if (layer.nodeIds && !layer.nodeIds.has(id)) continue; 246 // åºç¨åç§»éï¼ä½¿è¯¥å¾å±èç¹å¯¹é½å°ç¬¬ä¸ä¸ªå¾å±ç对åºä½ç½® 247 const adjustedX = pos.x + offsetX; 248 const adjustedY = pos.y + offsetY; 249 const worldPos = new THREE.Vector3( 250 adjustedX * 0.005, 251 yBase, 252 adjustedY * 0.015 253 ); 254 appState.positions.set(id, worldPos); 255 const obj = appState.nodeMeshes.get(id); 256 if (obj) { 257 obj.mesh.position.copy(worldPos); 258 if (obj.label) { 259 obj.label.position.set(worldPos.x, worldPos.y + appState.NODE_RADIUS + 0.28, worldPos.z); 260 } 261 } 262 } 263 }); 264 } 265 266 // 3. æ éåä¿®æ£ X åæ ï¼éå±è¿å 2D è¾¹ç¼å°è¾¹ç¼è¿çº¿å¤¹è§ 267 // child_3D_X = parent_adjusted_X + (child_base_X - parent_base_X) - parent_width * SCALE 268 { 269 const SCALE = 0.015, BASE_W = 120; 270 function applyEdgeOffset(node, parentId, parentBaseX, parentAdjustedX) { 271 const pos = appState.positions.get(node.id); 272 if (!pos) return; 273 const baseX = pos.x; 274 let adjustedX; 275 if (parentId && parentId !== appState.VIRTUAL_ROOT_ID) { 276 const pNode = appState.nodeMap.get(parentId); 277 adjustedX = pNode 278 ? parentAdjustedX + (baseX - parentBaseX) - (pNode.sizeScale || 1) * BASE_W * SCALE 279 : baseX; 280 } else { 281 adjustedX = baseX; 282 } 283 pos.x = adjustedX; 284 const obj = appState.nodeMeshes.get(node.id); 285 if (obj) { 286 obj.mesh.position.x = adjustedX; 287 if (obj.label) obj.label.position.x = adjustedX; 288 } 289 (node.children || []).forEach(c => applyEdgeOffset(c, node.id, baseX, adjustedX)); 290 } 291 applyEdgeOffset(appState.methodsTree, null, 0, 0); 292 } 293 294 // 4. 为æ¯å±èç¹ç»å¶åéæé«äº®ç©å½¢ 295 { 296 // æ¸ é¤æ§çé«äº®ç©å½¢ 297 if (appState.layerHighlights) { 298 appState.layerHighlights.forEach(h => appState.scene.remove(h)); 299 } 300 appState.layerHighlights = []; 301 302 sortedLayers.forEach((layer, layerIdx) => { 303 const yBase = layerIdx * LAYER_SPACING; 304 let minX = Infinity, maxX = -Infinity, minZ = Infinity, maxZ = -Infinity; 305 let hasNode = false; 306 307 for (const id of (layer.nodeIds || [])) { 308 const pos = appState.positions.get(id); 309 if (!pos) continue; 310 hasNode = true; 311 if (pos.x < minX) minX = pos.x; 312 if (pos.x > maxX) maxX = pos.x; 313 if (pos.z < minZ) minZ = pos.z; 314 if (pos.z > maxZ) maxZ = pos.z; 315 } 316 317 if (!hasNode) return; 318 319 const PAD = 0.5; 320 const w = maxX - minX + PAD * 2; 321 const h = maxZ - minZ + PAD * 2; 322 const geom = new THREE.PlaneGeometry(Math.max(w, 0.1), Math.max(h, 0.1), 64, 64); 323 324 // æ°´é¢æ³¢çº¹çè²å¨ 325 const waterUniforms = { 326 uTime: { value: 0 }, 327 uColor: { value: new THREE.Color(0x4488ff) }, 328 uOpacity: { value: 0.45 }, 329 uHighlight: { value: 0.0 } 330 }; 331 const waterMat = new THREE.ShaderMaterial({ 332 uniforms: waterUniforms, 333 transparent: true, 334 side: THREE.DoubleSide, 335 depthWrite: false, 336 vertexShader: ` 337 uniform float uTime; 338 varying vec2 vUv; 339 varying float vWave; 340 void main() { 341 vUv = uv; 342 vec3 pos = position; 343 // å¤å±æ³¢çº¹å å 344 float wave1 = sin(pos.x * 6.0 + uTime * 1.2) * cos(pos.y * 5.0 + uTime * 0.8) * 0.015; 345 float wave2 = sin(pos.x * 10.0 - uTime * 1.5) * sin(pos.y * 8.0 + uTime * 1.1) * 0.008; 346 float wave3 = cos(pos.x * 3.0 + pos.y * 4.0 + uTime * 0.6) * 0.01; 347 pos.z += wave1 + wave2 + wave3; 348 vWave = wave1 + wave2 + wave3; 349 gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0
349); 350 } 351 `, 352 fragmentShader: ` 353 uniform float uTime; 354 uniform vec3 uColor; 355 uniform float uOpacity; 356 uniform float uHighlight; 357 varying vec2 vUv; 358 varying float vWave; 359 void main() { 360 // æµå¨æ³å 361 float flow1 = sin(vUv.x * 12.0 + uTime * 1.5) * cos(vUv.y * 10.0 - uTime * 1.0); 362 float flow2 = sin(vUv.x * 8.0 - uTime * 0.7 + vUv.y * 6.0) * 0.5; 363 float flow = (flow1 + flow2) * 0.5 + 0.5; 364 365 // èªç±è¿å¨æ³¢çº¹ 366 float ripple = sin(length(vUv - 0.5) * 20.0 - uTime * 3.0) * 0.5 + 0.5; 367 ripple *= smoothstep(0.5, 0.0, length(vUv - 0.5)); 368 369 // ä¸å¿æ³å 370 float glow = smoothstep(0.6, 0.0, length(vUv - 0.5)) * 0.3; 371 372 // 波峰é«å 373 float specular = pow(max(0.0, vWave * 30.0), 2.0) * 0.4; 374 375 float alpha = (0.15 + flow * 0.12 + ripple * 0.1 + glow + specular) * uOpacity; 376 alpha = clamp(alpha, 0.0, 0.85); 377 378 vec3 col = uColor + specular * vec3(0.3, 0.5, 1.0); 379 col += uHighlight * vec3(0.1, 0.2, 0.4); 380 381 gl_FragColor = vec4(col, alpha); 382 } 383 ` 384 }); 385 386 const plane = new THREE.Mesh(geom, waterMat); 387 plane.rotation.x = -Math.PI / 2; // å¹³æ¾å¨ XZ å¹³é¢ 388 plane.position.set((minX + maxX) / 2, yBase, (minZ + maxZ) / 2); 389 plane.renderOrder = 999; 390 appState.scene.add(plane); 391 appState.layerHighlights.push(plane); 392 }); 393 } 394 395 // 5. ç»å¶3Dç»ç¾¤ç©å½¢ 396 { 397 if (appState.groupRectMeshes) { 398 appState.groupRectMeshes.forEach(m => appState.scene.remove(m)); 399 } 400 appState.groupRectMeshes = []; 401 402 const X_SCALE = 0.005, Z_SCALE = 0.015; 403 for (const gr of groupRects) { 404 // æ¾å°ç»ç¾¤æå±å¾å± 405 let layerIdx = -1; 406 if (gr.layerId) { 407 layerIdx = sortedLayers.findIndex(l => l.id === gr.layerId); 408 } 409 if (layerIdx < 0) { 410 // æ layerId æ¶å°è¯éè¿ nodeIds æ¾å¾å± 411 for (let li = 0; li < sortedLayers.length; li++) { 412 const layer = sortedLayers[li]; 413 if (gr.nodeIds && gr.nodeIds.some(nid => layer.nodeIds && layer.nodeIds.has(nid))) { 414 layerIdx = li; 415 break; 416 } 417 } 418 } 419 if (layerIdx < 0) continue; 420 421 const yBase = layerIdx * LAYER_SPACING; 422 const cx = (gr.x + gr.width / 2) * X_SCALE; 423 const cz = (gr.y + gr.height / 2) * Z_SCALE; 424 const w = Math.max(gr.width * X_SCALE, 0.05); 425 const h = Math.max(gr.height * Z_SCALE, 0.05); 426 427 // åéæå¡«å 428 const geom = new THREE.PlaneGeometry(w, h); 429 const fillAlpha = gr.fillOpacity !== undefined ? gr.fillOpacity : 0.25; 430 const mat = new THREE.MeshBasicMaterial({ 431 color: gr.fillColor ? parseInt(gr.fillColor.slice(1), 16) : 0x4a3c7e, 432 transparent: true, 433 opacity: fillAlpha, 434 side: THREE.DoubleSide, 435 depthWrite: false 436 }); 437 const plane = new THREE.Mesh(geom, mat); 438 plane.rotation.x = -Math.PI / 2; 439 plane.position.set(cx, yBase + 0.01, cz); 440 plane.renderOrder = 998; 441 appState.scene.add(plane); 442 appState.groupRectMeshes.push(plane); 443 444 // è¾¹æ¡çº¿ 445 const edgeGeom = new THREE.EdgesGeometry(new THREE.PlaneGeometry(w, h)); 446 const edgeMat = new THREE.LineBasicMaterial({ 447 color: gr.borderColor ? parseInt(gr.borderColor.slice(1), 16) : 0x7a6aae, 448 transparent: true, 449 opacity: 0.8 450 }); 451 const edgeLine = new THREE.LineSegments(edgeGeom, edgeMat); 452 edgeLine.rotation.x = -Math.PI / 2; 453 edgeLine.position.set(cx, yBase + 0.02, cz); 454 edgeLine.renderOrder = 999; 455 appState.scene.add(edgeLine); 456 appState.groupRectMeshes.push(edgeLine); 457 } 458 } 459 460 // æ¾ç¤ºå¾å±æé® 461 const layerBtn = document.getElementById('layerIconBtn'); 462 if (layerBtn) layerBtn.style.display = ''; 463 464 // æ¢å¤å½åå¾å± 465 if (curLayer) { 466 appState.currentLayerId = curLayer.id; 467 appState.positions2D = curLayer.positions2D || new Map(); 468 } 469 470 // é建è¿çº¿ 471 rebuildAllLines(); 472 saveCurrentProjectData(); 473} 474 475 // ---------- èªå¨æåæé®ï¼ä»»å¡æ ï¼ ---------- 476 const arrangeBtn = document.getElementById('arrangeBtn'); 477 const arrangePopup = document.getElementById('arrangePopup'); 478 const arrangeTreeBtn = document.getElementById('arrangeTreeBtn'); 479 const arrangeAllLayersBtn = document.getElementById('arrangeAllLayersBtn'); 480 const arrange3DDefaultBtn = document.getElementById('arrange3DDefaultBtn'); 481 const arrange3D2DBtn = document.getElementById('arrange3D2DBtn'); 482 if (arrangeBtn && arrangePopup) { 483 arrangeBtn.addEventListener('click', (e) => { 484 e.stopPropagation(); 485 arrangePopup.classList.toggle('show'); 486 }); 487 488 document.addEventListener('pointerdown', (e) => { 489 if (!arrangePopup.contains(e.target) && e.target !== arrangeBtn) { 490 arrangePopup.classList.remove('show'); 491 } 492 }); 493 494 document.getElementById('arrangeTreeBtn')?.addEventListener('click', (e) => { 495 e.stopPropagation(); 496 if (appState.autoArrangeTreeLayout) appState.autoArrangeTreeLayout(); 497 }); 498 499 document.getElementById('arrangeAllLayersBtn')?.addEventListener('click', (e) => { 500 e.stopPropagation(); 501 arrangeAllLayers(); 502 }); 503 504 document.getElementById('arrange3DDefaultBtn')?.addEventListener('click', (e) => { 505 e.stopPropagation(); 506 arrange3DDefault(); 507 }); 508 509 document.getElementById('arrange3D2DBtn')?.addEventListener('click', (e) => { 510 e.stopPropagation(); 511 arrange3DWith2DLayout(); 512 }); 513 } 514} 515 516// ==================== 2D平移循ç¯ï¼ç»ä¸å¤çï¼ ==================== 517function start2DPanningLoop() { 518 function loop() { 519 if (appState.is2DView && appState.process2DPanning) { 520 appState.process2DPanning(); 521 } 522 processSidebar2DPanning(); 523 requestAnimationFrame(loop); 524 } 525 requestAnimationFrame(loop); 526} 527start2DPanningLoop();
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.