dev #3

Merged
linkong merged 9 commits from dev into main 2026-03-25 09:25:39 +00:00
3 changed files with 174 additions and 46 deletions
Showing only changes of commit 543fe35fbb - Show all commits

View File

@@ -54,10 +54,19 @@ export const CABLE_STATE = {
};
export const SATELLITE_CONFIG = {
maxCount: 2000,
dotSize: 1.5,
trailLength: 30,
apiPath: '/api/v1/visualization/geo/satellites'
maxCount: 5000,
trailLength: 10,
dotSize: 4,
ringSize: 0.07,
apiPath: '/api/v1/visualization/geo/satellites',
breathingSpeed: 0.08,
breathingScaleAmplitude: 0.15,
breathingOpacityMin: 0.5,
breathingOpacityMax: 0.8,
dotBreathingSpeed: 0.12,
dotBreathingScaleAmplitude: 0.2,
dotOpacityMin: 0.7,
dotOpacityMax: 1.0
};
export const PREDICTED_ORBIT_CONFIG = {

View File

@@ -14,7 +14,7 @@ import {
} 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, getShowCables } from './cables.js';
import { createSatellites, loadSatellites, updateSatellitePositions, toggleSatellites, toggleTrails, getShowSatellites, getSatelliteCount, selectSatellite, getSatelliteData, getSatellitePoints, setSatelliteRingState, updateLockedRingPosition, updateHoverRingPosition, getSatellitePositions, showPredictedOrbit, hidePredictedOrbit } from './satellites.js';
import { createSatellites, loadSatellites, updateSatellitePositions, toggleSatellites, toggleTrails, getShowSatellites, getSatelliteCount, selectSatellite, getSatelliteData, getSatellitePoints, setSatelliteRingState, updateLockedRingPosition, updateHoverRingPosition, getSatellitePositions, showPredictedOrbit, hidePredictedOrbit, updateBreathingPhase } from './satellites.js';
import { setupControls, getAutoRotate, getShowTerrain, zoomLevel, setAutoRotate, toggleAutoRotate, resetView } from './controls.js';
import { initInfoCard, showInfoCard, hideInfoCard, getCurrentType, setInfoCardNoBorder } from './info-card.js';
@@ -32,6 +32,9 @@ let lockedObject = null;
let lockedObjectType = null;
let dragStartTime = 0;
let isLongDrag = false;
let lastSatClickTime = 0;
let lastSatClickIndex = 0;
let lastSatClickPos = { x: 0, y: 0 };
export function clearLockedObject() {
hidePredictedOrbit();
@@ -44,6 +47,7 @@ export function clearLockedObject() {
lockedObjectType = null;
lockedSatellite = null;
lockedSatelliteIndex = null;
window.lockedSatelliteIndex = null;
cableLockedData = null;
}
@@ -135,6 +139,7 @@ export function init() {
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = CONFIG.defaultCameraZ;
window.camera = camera;
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false, powerPreference: 'high-performance' });
renderer.setSize(window.innerWidth, window.innerHeight);
@@ -430,8 +435,26 @@ function onClick(event, camera, renderer) {
setAutoRotate(false);
handleCableClick(clickedCable);
} else if (satIntersects.length > 0) {
const index = satIntersects[0].index;
const sat = selectSatellite(index);
const now = Date.now();
const clickX = event.clientX;
const clickY = event.clientY;
let selectedIndex;
if (satIntersects.length > 1 &&
now - lastSatClickTime < 500 &&
Math.abs(clickX - lastSatClickPos.x) < 30 &&
Math.abs(clickY - lastSatClickPos.y) < 30) {
const currentIdx = satIntersects.findIndex(s => s.index === lastSatClickIndex);
selectedIndex = satIntersects[(currentIdx + 1) % satIntersects.length].index;
} else {
selectedIndex = satIntersects[0].index;
}
lastSatClickTime = now;
lastSatClickIndex = selectedIndex;
lastSatClickPos = { x: clickX, y: clickY };
const sat = selectSatellite(selectedIndex);
if (sat && sat.properties) {
clearLockedObject();
@@ -439,13 +462,14 @@ function onClick(event, camera, renderer) {
lockedObject = sat;
lockedObjectType = 'satellite';
lockedSatellite = sat;
lockedSatelliteIndex = index;
lockedSatelliteIndex = selectedIndex;
window.lockedSatelliteIndex = selectedIndex;
showPredictedOrbit(sat);
setAutoRotate(false);
const satPositions = getSatellitePositions();
if (satPositions && satPositions[index]) {
setSatelliteRingState(index, 'locked', satPositions[index].current);
if (satPositions && satPositions[selectedIndex]) {
setSatelliteRingState(selectedIndex, 'locked', satPositions[selectedIndex].current);
}
const props = sat.properties;
@@ -501,6 +525,9 @@ function animate() {
const satPositions = getSatellitePositions();
// 更新呼吸动画相位
updateBreathingPhase();
if (lockedObjectType === 'satellite' && lockedSatelliteIndex !== null) {
if (satPositions && satPositions[lockedSatelliteIndex]) {
updateLockedRingPosition(satPositions[lockedSatelliteIndex].current);

View File

@@ -14,13 +14,19 @@ let selectedSatellite = null;
let satellitePositions = [];
let hoverRingSprite = null;
let lockedRingSprite = null;
let lockedDotSprite = null;
export let breathingPhase = 0;
export function updateBreathingPhase() {
breathingPhase += SATELLITE_CONFIG.breathingSpeed;
}
const SATELLITE_API = SATELLITE_CONFIG.apiPath + '?limit=' + SATELLITE_CONFIG.maxCount;
const MAX_SATELLITES = SATELLITE_CONFIG.maxCount;
const TRAIL_LENGTH = SATELLITE_CONFIG.trailLength;
const DOT_TEXTURE_SIZE = 32;
function createCircularDotTexture() {
function createDotTexture() {
const canvas = document.createElement('canvas');
canvas.width = DOT_TEXTURE_SIZE;
canvas.height = DOT_TEXTURE_SIZE;
@@ -68,7 +74,7 @@ export function createSatellites(scene, earthObj) {
const positions = new Float32Array(MAX_SATELLITES * 3);
const colors = new Float32Array(MAX_SATELLITES * 3);
const dotTexture = createCircularDotTexture();
const dotTexture = createDotTexture();
const pointsGeometry = new THREE.BufferGeometry();
pointsGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
@@ -80,13 +86,27 @@ export function createSatellites(scene, earthObj) {
vertexColors: true,
transparent: true,
opacity: 0.9,
sizeAttenuation: true,
sizeAttenuation: false,
alphaTest: 0.1
});
satellitePoints = new THREE.Points(pointsGeometry, pointsMaterial);
satellitePoints.visible = false;
satellitePoints.userData = { type: 'satellitePoints' };
const originalScale = { x: 1, y: 1, z: 1 };
satellitePoints.onBeforeRender = (renderer, scene, camera, geometry, material) => {
if (earthObj && earthObj.scale.x !== 1) {
satellitePoints.scale.set(
originalScale.x / earthObj.scale.x,
originalScale.y / earthObj.scale.y,
originalScale.z / earthObj.scale.z
);
} else {
satellitePoints.scale.set(originalScale.x, originalScale.y, originalScale.z);
}
};
earthObj.add(satellitePoints);
const trailPositions = new Float32Array(MAX_SATELLITES * TRAIL_LENGTH * 3);
@@ -112,7 +132,9 @@ export function createSatellites(scene, earthObj) {
for (let i = 0; i < MAX_SATELLITES; i++) {
satellitePositions.push({
current: new THREE.Vector3(),
trail: []
trail: [],
trailIndex: 0,
trailCount: 0
});
}
@@ -149,12 +171,8 @@ function computeSatellitePosition(satellite, time) {
const tleLine1 = `1 ${noradId.toString().padStart(5)}U 00001A ${epochStr} .00000000 00000-0 00000-0 0 9999`;
const tleLine2 = `2 ${noradId.toString().padStart(5)} ${raan.toFixed(4).padStart(8)} ${inclination.toFixed(4).padStart(8)} ${eccStr.substring(1)} ${argOfPerigee.toFixed(4).padStart(8)} ${meanAnomaly.toFixed(4).padStart(8)} ${meanMotion.toFixed(8).padStart(11)} 0 9999`;
console.log('[DEBUG computeSat] TLE1:', tleLine1);
console.log('[DEBUG computeSat] TLE2:', tleLine2);
const satrec = twoline2satrec(tleLine1, tleLine2);
console.log('[DEBUG computeSat] satrec.error:', satrec?.error, 'satrec.no:', satrec?.no);
if (!satrec || satrec.error) {
console.log('[DEBUG computeSat] returning null due to satrec error');
return null;
}
@@ -255,9 +273,11 @@ export function updateSatellitePositions(deltaTime = 0) {
satellitePositions[i].current.copy(pos);
satellitePositions[i].trail.push(pos.clone());
if (satellitePositions[i].trail.length > TRAIL_LENGTH) {
satellitePositions[i].trail.shift();
const satPos = satellitePositions[i];
if (i !== window.lockedSatelliteIndex) {
satPos.trail[satPos.trailIndex] = pos.clone();
satPos.trailIndex = (satPos.trailIndex + 1) % TRAIL_LENGTH;
if (satPos.trailCount < TRAIL_LENGTH) satPos.trailCount++;
}
positions[i * 3] = pos.x;
@@ -287,20 +307,33 @@ export function updateSatellitePositions(deltaTime = 0) {
colors[i * 3 + 1] = g;
colors[i * 3 + 2] = b;
const trail = satellitePositions[i].trail;
const sp = satellitePositions[i];
const trail = sp.trail;
const tc = sp.trailCount;
const ti = sp.trailIndex;
for (let j = 0; j < TRAIL_LENGTH; j++) {
const trailIdx = (i * TRAIL_LENGTH + j) * 3;
if (j < trail.length) {
const t = trail[j];
trailPositions[trailIdx] = t.x;
trailPositions[trailIdx + 1] = t.y;
trailPositions[trailIdx + 2] = t.z;
const alpha = j / trail.length;
trailColors[trailIdx] = r * alpha;
trailColors[trailIdx + 1] = g * alpha;
trailColors[trailIdx + 2] = b * alpha;
if (j < tc) {
const idx = (ti - tc + j + TRAIL_LENGTH) % TRAIL_LENGTH;
const t = trail[idx];
if (t) {
trailPositions[trailIdx] = t.x;
trailPositions[trailIdx + 1] = t.y;
trailPositions[trailIdx + 2] = t.z;
const alpha = (j + 1) / tc;
trailColors[trailIdx] = r * alpha;
trailColors[trailIdx + 1] = g * alpha;
trailColors[trailIdx + 2] = b * alpha;
} else {
trailPositions[trailIdx] = pos.x;
trailPositions[trailIdx + 1] = pos.y;
trailPositions[trailIdx + 2] = pos.z;
trailColors[trailIdx] = 0;
trailColors[trailIdx + 1] = 0;
trailColors[trailIdx + 2] = 0;
}
} else {
trailPositions[trailIdx] = pos.x;
trailPositions[trailIdx + 1] = pos.y;
@@ -312,7 +345,7 @@ export function updateSatellitePositions(deltaTime = 0) {
}
}
for (let i = count; i < 2000; i++) {
for (let i = count; i < MAX_SATELLITES; i++) {
positions[i * 3] = 0;
positions[i * 3 + 1] = 0;
positions[i * 3 + 2] = 0;
@@ -393,12 +426,19 @@ export function showHoverRing(position, isLocked = false) {
map: ringTexture,
transparent: true,
opacity: 0.8,
depthTest: false
depthTest: false,
sizeAttenuation: false
});
const ringSize = SATELLITE_CONFIG.ringSize;
const sprite = new THREE.Sprite(spriteMaterial);
sprite.position.copy(position);
sprite.scale.set(3, 3, 1);
const camera = window.camera;
const cameraDistance = camera ? camera.position.distanceTo(position) : 400;
const scale = ringSize;
sprite.scale.set(scale, scale, 1);
console.log(`[Ring create] ringSize: ${ringSize}, camDist: ${cameraDistance}, scale: ${scale}`);
earthObjRef.add(sprite);
@@ -407,6 +447,24 @@ export function showHoverRing(position, isLocked = false) {
earthObjRef.remove(lockedRingSprite);
}
lockedRingSprite = sprite;
if (lockedDotSprite) {
earthObjRef.remove(lockedDotSprite);
}
const dotCanvas = createBrighterDotCanvas();
const dotTexture = new THREE.CanvasTexture(dotCanvas);
dotTexture.needsUpdate = true;
const dotMaterial = new THREE.SpriteMaterial({
map: dotTexture,
transparent: true,
opacity: 1.0,
depthTest: false
});
lockedDotSprite = new THREE.Sprite(dotMaterial);
lockedDotSprite.position.copy(position);
lockedDotSprite.scale.set(4 * cameraDistance / 200, 4 * cameraDistance / 200, 1);
earthObjRef.add(lockedDotSprite);
} else {
if (hoverRingSprite) {
earthObjRef.remove(hoverRingSprite);
@@ -417,6 +475,23 @@ export function showHoverRing(position, isLocked = false) {
return sprite;
}
function createBrighterDotCanvas() {
const size = DOT_TEXTURE_SIZE * 2;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
const center = size / 2;
const gradient = ctx.createRadialGradient(center, center, 0, center, center, center);
gradient.addColorStop(0, 'rgba(255, 255, 200, 1)');
gradient.addColorStop(0.3, 'rgba(255, 220, 100, 0.9)');
gradient.addColorStop(0.7, 'rgba(255, 180, 50, 0.5)');
gradient.addColorStop(1, 'rgba(255, 150, 0, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, size, size);
return canvas;
}
export function hideHoverRings() {
if (!earthObjRef) return;
@@ -427,20 +502,45 @@ export function hideHoverRings() {
}
export function hideLockedRing() {
if (!earthObjRef || !lockedRingSprite) return;
earthObjRef.remove(lockedRingSprite);
lockedRingSprite = null;
if (!earthObjRef) return;
if (lockedRingSprite) {
earthObjRef.remove(lockedRingSprite);
lockedRingSprite = null;
}
if (lockedDotSprite) {
earthObjRef.remove(lockedDotSprite);
lockedDotSprite = null;
}
}
export function updateLockedRingPosition(position) {
const ringSize = SATELLITE_CONFIG.ringSize;
const camera = window.camera;
const cameraDistance = camera ? camera.position.distanceTo(position) : 400;
if (lockedRingSprite && position) {
lockedRingSprite.position.copy(position);
const breathScale = 1 + Math.sin(breathingPhase) * SATELLITE_CONFIG.breathingScaleAmplitude;
lockedRingSprite.scale.set(ringSize * breathScale, ringSize * breathScale, 1);
const breathOpacity = SATELLITE_CONFIG.breathingOpacityMin + Math.sin(breathingPhase) * (SATELLITE_CONFIG.breathingOpacityMax - SATELLITE_CONFIG.breathingOpacityMin);
lockedRingSprite.material.opacity = breathOpacity;
}
if (lockedDotSprite && position) {
lockedDotSprite.position.copy(position);
const dotBreathScale = 1 + Math.sin(breathingPhase) * SATELLITE_CONFIG.dotBreathingScaleAmplitude;
lockedDotSprite.scale.set(4 * cameraDistance / 200 * dotBreathScale, 4 * cameraDistance / 200 * dotBreathScale, 1);
lockedDotSprite.material.opacity = SATELLITE_CONFIG.dotOpacityMin + Math.sin(breathingPhase) * (SATELLITE_CONFIG.dotOpacityMax - SATELLITE_CONFIG.dotOpacityMin);
}
}
export function updateHoverRingPosition(position) {
const ringSize = SATELLITE_CONFIG.ringSize;
const camera = window.camera;
const cameraDistance = camera ? camera.position.distanceTo(position) : 400;
const scale = ringSize;
if (hoverRingSprite && position) {
hoverRingSprite.position.copy(position);
hoverRingSprite.scale.set(scale, scale, 1);
console.log(`[Hover update] ringSize: ${ringSize}, camDist: ${cameraDistance}, scale: ${scale}`);
}
}
@@ -512,16 +612,8 @@ export function showPredictedOrbit(satellite) {
const props = satellite.properties;
const meanMotion = props?.mean_motion || 15;
const periodSeconds = calculateOrbitalPeriod(meanMotion);
console.log('[DEBUG] meanMotion:', meanMotion, 'periodSeconds:', periodSeconds);
// Test current time
const now = new Date();
const testPos = computeSatellitePosition(satellite, now);
console.log('[DEBUG] testPos (now):', testPos);
const points = calculatePredictedOrbit(satellite, periodSeconds);
console.log('[DEBUG] points.length:', points.length);
if (points.length < 2) return;
const positions = new Float32Array(points.length * 3);