From d676d34cd80d05c3a767b75a1b2efd4c18a3e4ef Mon Sep 17 00:00:00 2001 From: root Date: Tue, 28 Apr 2026 18:07:12 +0800 Subject: [PATCH] feat: fly-to-node on click and highlight read, autoView toggle respects fitCameraToGraph --- ui/static/graph.html | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/ui/static/graph.html b/ui/static/graph.html index 568e324..ef698f2 100644 --- a/ui/static/graph.html +++ b/ui/static/graph.html @@ -947,6 +947,10 @@ function init() { } else { selectedNode = node; showNodeInfo(node.userData.nodeData, true); + // 自动视角:点击节点时飞向它 + if (autoViewEnabled) { + flyToNode(node.userData.nodeId, 500); + } } } } @@ -1210,6 +1214,31 @@ function smoothReposition() { } }); }); + + // 自动视角:高亮节点被读取时,飞向它们的中心 + if (autoViewEnabled && currentHighlightIds.length > 0 && !data.new_node_id) { + const meshes = nodeMeshes.filter(m => currentHighlightIds.includes(m.userData.nodeId)); + if (meshes.length > 0) { + const avgPos = new THREE.Vector3(0, 0, 0); + meshes.forEach(m => avgPos.add(m.position)); + avgPos.divideScalar(meshes.length); + const distance = avgPos.length() + 25; + const startPos = camera.position.clone(); + const endCamPos = new THREE.Vector3(avgPos.x, avgPos.y + distance * 0.4, avgPos.z + distance * 0.8); + const startTarget = controls.target.clone(); + const duration = 600; + const startTime = Date.now(); + function lerpHighlight() { + const elapsed = Date.now() - startTime; + const t = Math.min(elapsed / duration, 1); + const ease = 1 - Math.pow(1 - t, 3); + camera.position.lerpVectors(startPos, endCamPos, ease); + controls.target.lerpVectors(startTarget, avgPos, ease); + if (t < 1) requestAnimationFrame(lerpHighlight); + } + lerpHighlight(); + } + } } if (!data.success) return;