// controls.js - Zoom, rotate and toggle controls import { CONFIG } from './constants.js'; import { updateZoomDisplay, showStatusMessage } from './ui.js'; import { toggleTerrain } from './earth.js'; import { reloadData } from './main.js'; import { toggleSatellites, toggleTrails, getShowSatellites, getSatelliteCount } from './satellites.js'; export let autoRotate = true; export let zoomLevel = 1.0; export let showTerrain = false; export let isDragging = false; let earthObj = null; export function setupControls(camera, renderer, scene, earth) { earthObj = earth; setupZoomControls(camera); setupWheelZoom(camera, renderer); setupRotateControls(camera, earth); setupTerrainControls(); } function setupZoomControls(camera) { let zoomInterval = null; let lastDirection = 0; let isSnapped = false; const MIN_PERCENT = CONFIG.minZoom * 100; const MAX_PERCENT = CONFIG.maxZoom * 100; function adjustZoom(direction) { const currentPercent = Math.round(zoomLevel * 100); let newPercent; if (direction > 0) { if (!isSnapped || currentPercent % 10 !== 0) { newPercent = Math.ceil(currentPercent / 10) * 10; if (newPercent <= currentPercent) newPercent = currentPercent + 10; isSnapped = true; } else { newPercent = currentPercent + 10; } } else { if (!isSnapped || currentPercent % 10 !== 0) { newPercent = Math.floor(currentPercent / 10) * 10; if (newPercent >= currentPercent) newPercent = currentPercent - 10; isSnapped = true; } else { newPercent = currentPercent - 10; } } if (newPercent > MAX_PERCENT) newPercent = MAX_PERCENT; if (newPercent < MIN_PERCENT) newPercent = MIN_PERCENT; zoomLevel = newPercent / 100; applyZoom(camera); } function startZoom(direction) { isSnapped = false; lastDirection = direction; adjustZoom(direction); zoomInterval = setInterval(() => { adjustZoom(direction); }, 150); } function stopZoom() { if (zoomInterval) { clearInterval(zoomInterval); zoomInterval = null; } } document.getElementById('zoom-in').addEventListener('mousedown', () => startZoom(1)); document.getElementById('zoom-in').addEventListener('mouseup', stopZoom); document.getElementById('zoom-in').addEventListener('mouseleave', stopZoom); document.getElementById('zoom-in').addEventListener('touchstart', (e) => { e.preventDefault(); startZoom(1); }); document.getElementById('zoom-in').addEventListener('touchend', stopZoom); document.getElementById('zoom-out').addEventListener('mousedown', () => startZoom(-1)); document.getElementById('zoom-out').addEventListener('mouseup', stopZoom); document.getElementById('zoom-out').addEventListener('mouseleave', stopZoom); document.getElementById('zoom-out').addEventListener('touchstart', (e) => { e.preventDefault(); startZoom(-1); }); document.getElementById('zoom-out').addEventListener('touchend', stopZoom); document.getElementById('zoom-value').addEventListener('click', function() { const startZoom = zoomLevel; const targetZoom = 1.0; const startDistance = CONFIG.defaultCameraZ / startZoom; const targetDistance = CONFIG.defaultCameraZ / targetZoom; animateValue(0, 1, 600, (progress) => { const ease = 1 - Math.pow(1 - progress, 3); zoomLevel = startZoom + (targetZoom - startZoom) * ease; camera.position.z = CONFIG.defaultCameraZ / zoomLevel; const distance = startDistance + (targetDistance - startDistance) * ease; updateZoomDisplay(zoomLevel, distance.toFixed(0)); }, () => { zoomLevel = 1.0; showStatusMessage('缩放已重置到100%', 'info'); }); }); const slider = document.getElementById('zoom-slider'); slider?.addEventListener('input', (e) => { zoomLevel = parseFloat(e.target.value); applyZoom(camera); }); } function setupWheelZoom(camera, renderer) { renderer.domElement.addEventListener('wheel', (e) => { e.preventDefault(); if (e.deltaY < 0) { zoomLevel = Math.min(zoomLevel + 0.1, CONFIG.maxZoom); } else { zoomLevel = Math.max(zoomLevel - 0.1, CONFIG.minZoom); } applyZoom(camera); }, { passive: false }); } function applyZoom(camera) { camera.position.z = CONFIG.defaultCameraZ / zoomLevel; const distance = camera.position.z.toFixed(0); updateZoomDisplay(zoomLevel, distance); } function animateValue(start, end, duration, onUpdate, onComplete) { const startTime = performance.now(); function update(currentTime) { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); const easeProgress = 1 - Math.pow(1 - progress, 3); const current = start + (end - start) * easeProgress; onUpdate(current); if (progress < 1) { requestAnimationFrame(update); } else if (onComplete) { onComplete(); } } requestAnimationFrame(update); } export function resetView(camera) { if (!earthObj) return; const startRotX = earthObj.rotation.x; const startRotY = earthObj.rotation.y; const startZoom = zoomLevel; const targetRotX = 23.5 * Math.PI / 180; const targetRotY = 0; const targetZoom = 1.0; animateValue(0, 1, 800, (progress) => { const ease = 1 - Math.pow(1 - progress, 3); earthObj.rotation.x = startRotX + (targetRotX - startRotX) * ease; earthObj.rotation.y = startRotY + (targetRotY - startRotY) * ease; zoomLevel = startZoom + (targetZoom - startZoom) * ease; camera.position.z = CONFIG.defaultCameraZ / zoomLevel; updateZoomDisplay(zoomLevel, camera.position.z.toFixed(0)); }, () => { zoomLevel = 1.0; showStatusMessage('视图已重置', 'info'); }); if (typeof window.clearLockedCable === 'function') { window.clearLockedCable(); } } function setupRotateControls(camera, earth) { const rotateBtn = document.getElementById('rotate-toggle'); const tooltip = rotateBtn?.querySelector('.tooltip'); function updateRotateIcon() { const isRotating = getAutoRotate(); rotateBtn.innerHTML = isRotating ? '⏸️' : '▶️'; if (tooltip) { tooltip.textContent = isRotating ? '暂停旋转' : '开始旋转'; } } rotateBtn.addEventListener('click', function() { toggleAutoRotate(); const isRotating = getAutoRotate(); updateRotateIcon(); showStatusMessage(isRotating ? '自动旋转已开启' : '自动旋转已暂停', 'info'); }); updateRotateIcon(); document.getElementById('reset-view').addEventListener('click', function() { resetView(camera); }); } function setupTerrainControls() { document.getElementById('toggle-terrain').addEventListener('click', function() { showTerrain = !showTerrain; toggleTerrain(showTerrain); this.classList.toggle('active', showTerrain); this.querySelector('.tooltip').textContent = showTerrain ? '隐藏地形' : '显示地形'; document.getElementById('terrain-status').textContent = showTerrain ? '开启' : '关闭'; showStatusMessage(showTerrain ? '地形已显示' : '地形已隐藏', 'info'); }); document.getElementById('toggle-satellites').addEventListener('click', function() { const showSats = !getShowSatellites(); toggleSatellites(showSats); this.classList.toggle('active', showSats); this.querySelector('.tooltip').textContent = showSats ? '隐藏卫星' : '显示卫星'; document.getElementById('satellite-count').textContent = getSatelliteCount() + ' 颗'; showStatusMessage(showSats ? '卫星已显示' : '卫星已隐藏', 'info'); }); document.getElementById('toggle-trails').addEventListener('click', function() { const isActive = this.classList.contains('active'); const showTrails = !isActive; toggleTrails(showTrails); this.classList.toggle('active', showTrails); this.querySelector('.tooltip').textContent = showTrails ? '隐藏轨迹' : '显示轨迹'; showStatusMessage(showTrails ? '轨迹已显示' : '轨迹已隐藏', 'info'); }); document.getElementById('reload-data').addEventListener('click', async () => { await reloadData(); showStatusMessage('数据已重新加载', 'success'); }); const toolbarToggle = document.getElementById('toolbar-toggle'); const toolbar = document.getElementById('control-toolbar'); if (toolbarToggle && toolbar) { toolbarToggle.addEventListener('click', () => { toolbar.classList.toggle('collapsed'); }); } } function setupMouseControls(camera, renderer) { let previousMousePosition = { x: 0, y: 0 }; renderer.domElement.addEventListener('mousedown', (e) => { isDragging = true; previousMousePosition = { x: e.clientX, y: e.clientY }; }); renderer.domElement.addEventListener('mouseup', () => { isDragging = false; }); renderer.domElement.addEventListener('mousemove', (e) => { if (isDragging) { const deltaX = e.clientX - previousMousePosition.x; const deltaY = e.clientY - previousMousePosition.y; if (earth) { earth.rotation.y += deltaX * 0.005; earth.rotation.x += deltaY * 0.005; } previousMousePosition = { x: e.clientX, y: e.clientY }; } }); } export function getAutoRotate() { return autoRotate; } export function setAutoRotate(value) { autoRotate = value; const btn = document.getElementById('rotate-toggle'); if (btn) { btn.classList.toggle('active', value); const tooltip = btn.querySelector('.tooltip'); if (tooltip) tooltip.textContent = value ? '暂停旋转' : '自动旋转'; } } export function toggleAutoRotate() { autoRotate = !autoRotate; const btn = document.getElementById('rotate-toggle'); if (btn) { btn.classList.toggle('active', autoRotate); const tooltip = btn.querySelector('.tooltip'); if (tooltip) tooltip.textContent = autoRotate ? '暂停旋转' : '自动旋转'; } if (window.clearLockedCable) { window.clearLockedCable(); } return autoRotate; } export function getZoomLevel() { return zoomLevel; } export function getShowTerrain() { return showTerrain; }