Files
planet/frontend/public/earth/js/controls.js

325 lines
11 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';
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;
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');
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;
}