- Add box-sizing/padding normalization to toolbar buttons - Remove zoom slider, implement click/hold zoom behavior (+/- buttons) - Add 10% step on click, 1% continuous on hold - Fix satellite init: show satellite points immediately, delay trail visibility - Fix breathing effect: faster pulse, wider opacity range - Add toggle-cables functionality with visibility state - Initialize satellites and cables as visible by default
304 lines
10 KiB
JavaScript
304 lines
10 KiB
JavaScript
// 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;
|
|
}
|