import * as THREE from 'three'; import { createNoise3D } from 'simplex-noise'; import { CONFIG, CABLE_CONFIG, CABLE_STATE } 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, lockedCable as cableLocked, getCableState, setCableState, clearAllCableStates, applyLandingPointVisualState, resetLandingPointVisualState, getAllLandingPoints } from './cables.js'; import { createSatellites, loadSatellites, updateSatellitePositions, toggleSatellites, toggleTrails, getShowSatellites, selectSatellite, getSatelliteData, getSatellitePoints, setSatelliteRingState, updateLockedRingPosition, updateHoverRingPosition, getSatellitePositions } from './satellites.js'; import { setupControls, getAutoRotate, getShowTerrain, zoomLevel, setAutoRotate, toggleAutoRotate, resetView } 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 hoveredSatellite = null; let hoveredSatelliteIndex = null; let cableLockedData = null; let lockedSatellite = null; let lockedSatelliteIndex = null; let lockedObject = null; let lockedObjectType = null; let dragStartTime = 0; let isLongDrag = false; function clearLockedObject() { hoveredCable = null; hoveredSatellite = null; hoveredSatelliteIndex = null; clearAllCableStates(); setSatelliteRingState(null, 'none', null); lockedObject = null; lockedObjectType = null; lockedSatellite = null; lockedSatelliteIndex = null; cableLockedData = null; } function isSameCable(cable1, cable2) { if (!cable1 || !cable2) return false; const id1 = cable1.userData?.cableId; const id2 = cable2.userData?.cableId; if (id1 === undefined || id2 === undefined) return false; return id1 === id2; } function showCableInfo(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 }); } function showSatelliteInfo(props) { const meanMotion = props?.mean_motion || 0; const period = meanMotion > 0 ? (1440 / meanMotion).toFixed(1) : '-'; const ecc = props?.eccentricity || 0; const perigee = (6371 * (1 - ecc)).toFixed(0); const apogee = (6371 * (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 }); } function applyCableVisualState() { const allCables = getCableLines(); const pulse = (Math.sin(Date.now() * CABLE_CONFIG.pulseSpeed) + 1) * 0.5; allCables.forEach(c => { const cableId = c.userData.cableId; const state = getCableState(cableId); switch (state) { case CABLE_STATE.LOCKED: c.material.opacity = CABLE_CONFIG.lockedOpacityMin + pulse * CABLE_CONFIG.pulseCoefficient; c.material.color.setRGB(1, 1, 1); break; case CABLE_STATE.HOVERED: c.material.opacity = 1; c.material.color.setRGB(1, 1, 1); break; case CABLE_STATE.NORMAL: default: if ((lockedObjectType === 'cable' && lockedObject) || (lockedObjectType === 'satellite' && lockedSatellite)) { c.material.opacity = CABLE_CONFIG.otherOpacity; const origColor = c.userData.originalColor; const brightness = CABLE_CONFIG.otherBrightness; c.material.color.setRGB( ((origColor >> 16) & 255) / 255 * brightness, ((origColor >> 8) & 255) / 255 * brightness, (origColor & 255) / 255 * brightness ); } else { c.material.opacity = 1; c.material.color.setHex(c.userData.originalColor); } } }); } 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); resetView(camera); 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); const hasHoveredCable = intersects.length > 0; let hoveredSat = null; let hoveredSatIndexFromIntersect = null; if (getShowSatellites()) { const satPoints = getSatellitePoints(); if (satPoints) { const satIntersects = raycaster.intersectObject(satPoints); if (satIntersects.length > 0) { hoveredSatIndexFromIntersect = satIntersects[0].index; hoveredSat = selectSatellite(hoveredSatIndexFromIntersect); } } } const hasHoveredSatellite = hoveredSat && hoveredSat.properties; if (hoveredCable) { if (!hasHoveredCable || !isSameCable(intersects[0]?.object, hoveredCable)) { if (!isSameCable(hoveredCable, lockedObject)) { setCableState(hoveredCable.userData.cableId, CABLE_STATE.NORMAL); } hoveredCable = null; } } if (hoveredSatelliteIndex !== null && hoveredSatelliteIndex !== hoveredSatIndexFromIntersect) { if (hoveredSatelliteIndex !== lockedSatelliteIndex) { setSatelliteRingState(hoveredSatelliteIndex, 'none', null); } hoveredSatelliteIndex = null; } if (hasHoveredCable) { const cable = intersects[0].object; if (!isSameCable(cable, lockedObject)) { hoveredCable = cable; setCableState(cable.userData.cableId, CABLE_STATE.HOVERED); } else { hoveredCable = cable; } showCableInfo(cable); setInfoCardNoBorder(true); hideTooltip(); } else if (hasHoveredSatellite) { hoveredSatellite = hoveredSat; hoveredSatelliteIndex = hoveredSatIndexFromIntersect; if (hoveredSatelliteIndex !== lockedSatelliteIndex) { const satPositions = getSatellitePositions(); if (satPositions && satPositions[hoveredSatelliteIndex]) { setSatelliteRingState(hoveredSatelliteIndex, 'hover', satPositions[hoveredSatelliteIndex].current); } } showSatelliteInfo(hoveredSat.properties); setInfoCardNoBorder(true); } else if (lockedObjectType === 'cable' && lockedObject) { showCableInfo(lockedObject); } else if (lockedObjectType === 'satellite' && lockedSatellite) { if (lockedSatelliteIndex !== null && lockedSatelliteIndex !== undefined) { const satPositions = getSatellitePositions(); if (satPositions && satPositions[lockedSatelliteIndex]) { setSatelliteRingState(lockedSatelliteIndex, 'locked', satPositions[lockedSatelliteIndex].current); } } showSatelliteInfo(lockedSatellite.properties); } 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`); } } else { hideTooltip(); } if (isDragging) { if (Date.now() - dragStartTime > 500) { isLongDrag = true; } 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; dragStartTime = Date.now(); isLongDrag = false; 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); const satIntersects = getShowSatellites() ? raycaster.intersectObject(getSatellitePoints()) : []; if (intersects.length > 0) { clearLockedObject(); const clickedCable = intersects[0].object; const cableId = clickedCable.userData.cableId; setCableState(cableId, CABLE_STATE.LOCKED); lockedObject = clickedCable; lockedObjectType = 'cable'; cableLockedData = { ...clickedCable.userData }; setAutoRotate(false); handleCableClick(clickedCable); } else if (satIntersects.length > 0) { const index = satIntersects[0].index; const sat = selectSatellite(index); if (sat && sat.properties) { clearLockedObject(); lockedObject = sat; lockedObjectType = 'satellite'; lockedSatellite = sat; lockedSatelliteIndex = index; setAutoRotate(false); const satPositions = getSatellitePositions(); if (satPositions && satPositions[index]) { setSatelliteRingState(index, 'locked', satPositions[index].current); } 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 (!isLongDrag) { clearLockedObject(); setAutoRotate(true); clearCableSelection(); } } } function animate() { requestAnimationFrame(animate); const earth = getEarth(); if (getAutoRotate() && earth) { earth.rotation.y += CONFIG.rotationSpeed; } applyCableVisualState(); if (lockedObjectType === 'cable' && lockedObject) { applyLandingPointVisualState(lockedObject.userData.name, false); } else if (lockedObjectType === 'satellite' && lockedSatellite) { applyLandingPointVisualState(null, true); } else { resetLandingPointVisualState(); } updateSatellitePositions(16); const satPositions = getSatellitePositions(); if (lockedObjectType === 'satellite' && lockedSatelliteIndex !== null) { if (satPositions && satPositions[lockedSatelliteIndex]) { updateLockedRingPosition(satPositions[lockedSatelliteIndex].current); } } else if (hoveredSatelliteIndex !== null && satPositions && satPositions[hoveredSatelliteIndex]) { updateHoverRingPosition(satPositions[hoveredSatelliteIndex].current); } renderer.render(scene, camera); } window.clearLockedCable = function() { clearLockedObject(); }; window.clearSelection = function() { hideInfoCard(); window.clearLockedCable(); }; document.addEventListener('DOMContentLoaded', init);