// controls.js - Zoom, rotate and toggle controls import { CONFIG, EARTH_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'; import { toggleCables, getShowCables } from './cables.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 holdTimeout = null; let startTime = 0; const HOLD_THRESHOLD = 150; const LONG_PRESS_TICK = 50; const CLICK_STEP = 10; const MIN_PERCENT = CONFIG.minZoom * 100; const MAX_PERCENT = CONFIG.maxZoom * 100; function doZoomStep(direction) { let currentPercent = Math.round(zoomLevel * 100); let newPercent = direction > 0 ? currentPercent + CLICK_STEP : currentPercent - CLICK_STEP; if (newPercent > MAX_PERCENT) newPercent = MAX_PERCENT; if (newPercent < MIN_PERCENT) newPercent = MIN_PERCENT; zoomLevel = newPercent / 100; applyZoom(camera); } function doContinuousZoom(direction) { let currentPercent = Math.round(zoomLevel * 100); let newPercent = direction > 0 ? currentPercent + 1 : currentPercent - 1; if (newPercent > MAX_PERCENT) newPercent = MAX_PERCENT; if (newPercent < MIN_PERCENT) newPercent = MIN_PERCENT; zoomLevel = newPercent / 100; applyZoom(camera); } function startContinuousZoom(direction) { doContinuousZoom(direction); zoomInterval = setInterval(() => { doContinuousZoom(direction); }, LONG_PRESS_TICK); } function stopZoom() { if (zoomInterval) { clearInterval(zoomInterval); zoomInterval = null; } if (holdTimeout) { clearTimeout(holdTimeout); holdTimeout = null; } } function handleMouseDown(direction) { startTime = Date.now(); stopZoom(); holdTimeout = setTimeout(() => { startContinuousZoom(direction); }, HOLD_THRESHOLD); } function handleMouseUp(direction) { const heldTime = Date.now() - startTime; stopZoom(); if (heldTime < HOLD_THRESHOLD) { doZoomStep(direction); } } document.getElementById('zoom-in').addEventListener('mousedown', () => handleMouseDown(1)); document.getElementById('zoom-in').addEventListener('mouseup', () => handleMouseUp(1)); document.getElementById('zoom-in').addEventListener('mouseleave', stopZoom); document.getElementById('zoom-in').addEventListener('touchstart', (e) => { e.preventDefault(); handleMouseDown(1); }); document.getElementById('zoom-in').addEventListener('touchend', () => handleMouseUp(1)); document.getElementById('zoom-out').addEventListener('mousedown', () => handleMouseDown(-1)); document.getElementById('zoom-out').addEventListener('mouseup', () => handleMouseUp(-1)); document.getElementById('zoom-out').addEventListener('mouseleave', stopZoom); document.getElementById('zoom-out').addEventListener('touchstart', (e) => { e.preventDefault(); handleMouseDown(-1); }); document.getElementById('zoom-out').addEventListener('touchend', () => handleMouseUp(-1)); document.getElementById('zoom-value').addEventListener('click', function() { const startZoomVal = zoomLevel; const targetZoom = 1.0; const startDistance = CONFIG.defaultCameraZ / startZoomVal; const targetDistance = CONFIG.defaultCameraZ / targetZoom; animateValue(0, 1, 600, (progress) => { const ease = 1 - Math.pow(1 - progress, 3); zoomLevel = startZoomVal + (targetZoom - startZoomVal) * ease; camera.position.z = CONFIG.defaultCameraZ / zoomLevel; const distance = startDistance + (targetDistance - startDistance) * ease; updateZoomDisplay(zoomLevel, distance.toFixed(0)); }, () => { zoomLevel = 1.0; showStatusMessage('缩放已重置到100%', 'info'); }); }); } 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; function animateToView(targetLat, targetLon, targetRotLon) { const latRot = targetLat * Math.PI / 180; const targetRotX = EARTH_CONFIG.tiltRad + latRot * EARTH_CONFIG.latCoefficient; const targetRotY = -(targetRotLon * Math.PI / 180); const startRotX = earthObj.rotation.x; const startRotY = earthObj.rotation.y; const startZoom = zoomLevel; 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 (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (pos) => animateToView(pos.coords.latitude, pos.coords.longitude, -pos.coords.longitude), () => animateToView(EARTH_CONFIG.chinaLat, EARTH_CONFIG.chinaLon, EARTH_CONFIG.chinaRotLon), { timeout: 5000, enableHighAccuracy: false } ); } else { animateToView(EARTH_CONFIG.chinaLat, EARTH_CONFIG.chinaLon, EARTH_CONFIG.chinaRotLon); } if (typeof window.clearLockedCable === 'function') { window.clearLockedCable(); } } function setupRotateControls(camera, earth) { const rotateBtn = document.getElementById('rotate-toggle'); rotateBtn.addEventListener('click', function() { const isRotating = toggleAutoRotate(); showStatusMessage(isRotating ? '自动旋转已开启' : '自动旋转已暂停', 'info'); }); updateRotateUI(); 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('toggle-cables').addEventListener('click', function() { const showCables = !getShowCables(); toggleCables(showCables); this.classList.toggle('active', showCables); this.querySelector('.tooltip').textContent = showCables ? '隐藏线缆' : '显示线缆'; showStatusMessage(showCables ? '线缆已显示' : '线缆已隐藏', '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'); }); } } export function getAutoRotate() { return autoRotate; } function updateRotateUI() { const btn = document.getElementById('rotate-toggle'); if (btn) { btn.classList.toggle('active', autoRotate); btn.innerHTML = autoRotate ? '⏸️' : '▶️'; const tooltip = btn.querySelector('.tooltip'); if (tooltip) tooltip.textContent = autoRotate ? '暂停旋转' : '开始旋转'; } } export function setAutoRotate(value) { autoRotate = value; updateRotateUI(); } export function toggleAutoRotate() { autoRotate = !autoRotate; updateRotateUI(); if (window.clearLockedCable) { window.clearLockedCable(); } return autoRotate; } export function getZoomLevel() { return zoomLevel; } export function getShowTerrain() { return showTerrain; }