import * as THREE from 'three'; import { createNoise3D } from 'simplex-noise'; import { CONFIG } from './constants.js'; import { latLonToVector3, vector3ToLatLon, screenToEarthCoords } from './utils.js'; import { showStatusMessage, updateCoordinatesDisplay, updateZoomDisplay, updateEarthStats, setLoading, showTooltip, hideTooltip } from './ui.js'; import { createEarth, createClouds, createTerrain, createStars, createGridLines, toggleTerrain, getEarth } from './earth.js'; import { loadGeoJSONFromPath, loadLandingPoints, handleCableClick, clearCableSelection, getCableLines, getCablesById } from './cables.js'; import { createSatellites, loadSatellites, updateSatellitePositions, toggleSatellites, toggleTrails, getShowSatellites, selectSatellite, getSatelliteData, getSatellitePoints } from './satellites.js'; import { setupControls, getAutoRotate, getShowTerrain, zoomLevel, setAutoRotate, toggleAutoRotate } from './controls.js'; import { initInfoCard, showInfoCard, hideInfoCard, getCurrentType, setInfoCardNoBorder } from './info-card.js'; export let scene, camera, renderer; let simplex; let isDragging = false; let previousMousePosition = { x: 0, y: 0 }; let hoveredCable = null; let lockedCable = null; let lockedCableData = null; window.addEventListener('error', (e) => { console.error('全局错误:', e.error); showStatusMessage('加载错误: ' + e.error?.message, 'error'); }); window.addEventListener('unhandledrejection', (e) => { console.error('未处理的Promise错误:', e.reason); }); export function init() { simplex = createNoise3D(); scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = CONFIG.defaultCameraZ; renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false, powerPreference: 'high-performance' }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0x0a0a1a, 1); renderer.setPixelRatio(window.devicePixelRatio); document.getElementById('container').appendChild(renderer.domElement); addLights(); initInfoCard(); const earthObj = createEarth(scene); createClouds(scene, earthObj); createTerrain(scene, earthObj, simplex); createStars(scene); createGridLines(scene, earthObj); createSatellites(scene, earthObj); setupControls(camera, renderer, scene, earthObj); setupEventListeners(camera, renderer); loadData(); animate(); } function addLights() { const ambientLight = new THREE.AmbientLight(0x404060); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2); directionalLight.position.set(5, 3, 5); scene.add(directionalLight); const backLight = new THREE.DirectionalLight(0x446688, 0.3); backLight.position.set(-5, 0, -5); scene.add(backLight); const pointLight = new THREE.PointLight(0xffffff, 0.4); pointLight.position.set(10, 10, 10); scene.add(pointLight); } let earthTexture = null; async function loadData(showWhiteSphere = false) { if (showWhiteSphere) { const earth = getEarth(); if (earth && earth.material) { earthTexture = earth.material.map; earth.material.map = null; earth.material.color.setHex(0xffffff); earth.material.needsUpdate = true; } } setLoading(true); try { console.log('开始加载电缆数据...'); await loadGeoJSONFromPath(scene, getEarth()); console.log('电缆数据加载完成'); await loadLandingPoints(scene, getEarth()); console.log('登陆点数据加载完成'); const satCount = await loadSatellites(); console.log(`卫星数据加载完成: ${satCount} 颗`); updateSatellitePositions(); console.log('卫星位置已更新'); } catch (error) { console.error('加载数据失败:', error); showStatusMessage('加载数据失败: ' + error.message, 'error'); } setLoading(false); if (showWhiteSphere) { const earth = getEarth(); if (earth && earth.material) { earth.material.map = earthTexture; earth.material.color.setHex(0xffffff); earth.material.needsUpdate = true; } } } export async function reloadData() { await loadData(true); } function setupEventListeners(camera, renderer) { window.addEventListener('resize', () => onWindowResize(camera, renderer)); renderer.domElement.addEventListener('mousemove', (e) => onMouseMove(e, camera)); renderer.domElement.addEventListener('mousedown', onMouseDown); renderer.domElement.addEventListener('mouseup', onMouseUp); renderer.domElement.addEventListener('click', (e) => onClick(e, camera, renderer)); } function onWindowResize(camera, renderer) { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function getFrontFacingCables(cableLines, camera) { const earth = getEarth(); if (!earth) return cableLines; const cameraDir = new THREE.Vector3(); camera.getWorldDirection(cameraDir); return cableLines.filter(cable => { const cablePos = new THREE.Vector3(); cable.geometry.computeBoundingBox(); const boundingBox = cable.geometry.boundingBox; if (boundingBox) { boundingBox.getCenter(cablePos); cable.localToWorld(cablePos); } const toCamera = new THREE.Vector3().subVectors(camera.position, earth.position).normalize(); const toCable = new THREE.Vector3().subVectors(cablePos, earth.position).normalize(); return toCamera.dot(toCable) > 0; }); } function onMouseMove(event, camera) { const earth = getEarth(); if (!earth) return; const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2( (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1 ); raycaster.setFromCamera(mouse, camera); const allCableLines = getCableLines(); const frontCables = getFrontFacingCables(allCableLines, camera); const intersects = raycaster.intersectObjects(frontCables); if (hoveredCable && hoveredCable !== lockedCable) { const prevCableId = hoveredCable.userData.cableId; const prevSameCables = getCablesById(prevCableId); prevSameCables.forEach(c => { if (c.userData.originalColor !== undefined) { c.material.color.setHex(c.userData.originalColor); } }); hoveredCable = null; } if (intersects.length > 0) { const cable = intersects[0].object; const cableId = cable.userData.cableId; const sameCables = getCablesById(cableId); if (cable !== lockedCable) { sameCables.forEach(c => { c.material.color.setHex(0xffffff); c.material.opacity = 1; }); hoveredCable = cable; showInfoCard('cable', { name: cable.userData.name, owner: cable.userData.owner, status: cable.userData.status, length: cable.userData.length, coords: cable.userData.coords, rfs: cable.userData.rfs }); setInfoCardNoBorder(true); } const userData = cable.userData; hideTooltip(); } else { if (lockedCable) { handleCableClick(lockedCable); } else { hideInfoCard(); } const earthPoint = screenToEarthCoords(event.clientX, event.clientY, camera, earth); if (earthPoint) { const coords = vector3ToLatLon(earthPoint); updateCoordinatesDisplay(coords.lat, coords.lon, coords.alt); if (!isDragging) { showTooltip(event.clientX + 10, event.clientY + 10, `纬度: ${coords.lat}°
经度: ${coords.lon}°
海拔: ${coords.alt.toFixed(1)} km`); } } } if (isDragging) { const deltaX = event.clientX - previousMousePosition.x; const deltaY = event.clientY - previousMousePosition.y; earth.rotation.y += deltaX * 0.005; earth.rotation.x += deltaY * 0.005; previousMousePosition = { x: event.clientX, y: event.clientY }; } } function onMouseDown(event) { isDragging = true; previousMousePosition = { x: event.clientX, y: event.clientY }; document.getElementById('container').classList.add('dragging'); hideTooltip(); } function onMouseUp() { isDragging = false; document.getElementById('container').classList.remove('dragging'); } function onClick(event, camera, renderer) { const earth = getEarth(); if (!earth) return; const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2( (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1 ); raycaster.setFromCamera(mouse, camera); const allCableLines = getCableLines(); const frontCables = getFrontFacingCables(allCableLines, camera); const intersects = raycaster.intersectObjects(frontCables); if (intersects.length > 0) { if (lockedCable) { const prevCableId = lockedCable.userData.cableId; const prevSameCables = getCablesById(prevCableId); prevSameCables.forEach(c => { if (c.userData.originalColor !== undefined) { c.material.color.setHex(c.userData.originalColor); } }); } const clickedCable = intersects[0].object; const cableId = clickedCable.userData.cableId; const sameCables = getCablesById(cableId); sameCables.forEach(c => { c.material.color.setHex(0xffffff); c.material.opacity = 1; }); lockedCable = clickedCable; lockedCableData = { ...clickedCable.userData }; setAutoRotate(false); handleCableClick(clickedCable); showInfoCard('cable', { name: clickedCable.userData.name, owner: clickedCable.userData.owner, status: clickedCable.userData.status, length: clickedCable.userData.length, coords: clickedCable.userData.coords, rfs: clickedCable.userData.rfs }); } else if (getShowSatellites()) { const satIntersects = raycaster.intersectObject(getSatellitePoints()); if (satIntersects.length > 0) { const index = satIntersects[0].index; const sat = selectSatellite(index); if (sat && sat.properties) { const props = sat.properties; const meanMotion = props.mean_motion || 0; const period = meanMotion > 0 ? (1440 / meanMotion).toFixed(1) : '-'; const ecc = props.eccentricity || 0; const earthRadius = 6371; const perigee = (earthRadius * (1 - ecc)).toFixed(0); const apogee = (earthRadius * (1 + ecc)).toFixed(0); showInfoCard('satellite', { name: props.name, norad_id: props.norad_cat_id, inclination: props.inclination ? props.inclination.toFixed(2) : '-', period: period, perigee: perigee, apogee: apogee }); showStatusMessage('已选择: ' + props.name, 'info'); } } } else { if (lockedCable) { const prevCableId = lockedCable.userData.cableId; const prevSameCables = getCablesById(prevCableId); prevSameCables.forEach(c => { if (c.userData.originalColor !== undefined) { c.material.color.setHex(c.userData.originalColor); } }); lockedCable = null; lockedCableData = null; } setAutoRotate(true); clearCableSelection(); } } function animate() { requestAnimationFrame(animate); const earth = getEarth(); if (getAutoRotate() && earth) { earth.rotation.y += CONFIG.rotationSpeed; } if (lockedCable) { const pulse = (Math.sin(Date.now() * 0.003) + 1) * 0.5; const glowIntensity = 0.7 + pulse * 0.3; const cableId = lockedCable.userData.cableId; const sameCables = getCablesById(cableId); sameCables.forEach(c => { c.material.opacity = 0.6 + pulse * 0.4; c.material.color.setRGB(glowIntensity, glowIntensity, glowIntensity); }); } updateSatellitePositions(16); renderer.render(scene, camera); } window.clearLockedCable = function() { if (lockedCable) { const cableId = lockedCable.userData.cableId; const sameCables = getCablesById(cableId); sameCables.forEach(c => { if (c.userData.originalColor !== undefined) { c.material.color.setHex(c.userData.originalColor); c.material.opacity = 1.0; } }); lockedCable = null; lockedCableData = null; } clearCableSelection(); }; window.clearSelection = function() { hideInfoCard(); window.clearLockedCable(); }; document.addEventListener('DOMContentLoaded', init);