Enhance Earth interaction and bump version to 0.21.0

This commit is contained in:
linkong
2026-03-26 11:09:57 +08:00
parent a04f4f9e67
commit 7b53cf9a06
12 changed files with 211 additions and 56 deletions

View File

@@ -1 +1 @@
0.20.0 0.21.0

View File

@@ -5,7 +5,7 @@ Returns GeoJSON format compatible with Three.js, CesiumJS, and Unreal Cesium.
""" """
from datetime import datetime from datetime import datetime
from fastapi import APIRouter, HTTPException, Depends from fastapi import APIRouter, HTTPException, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func from sqlalchemy import select, func
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
@@ -400,7 +400,11 @@ async def get_all_geojson(db: AsyncSession = Depends(get_db)):
@router.get("/geo/satellites") @router.get("/geo/satellites")
async def get_satellites_geojson( async def get_satellites_geojson(
limit: int = 10000, limit: Optional[int] = Query(
None,
ge=1,
description="Maximum number of satellites to return. Omit for no limit.",
),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
): ):
"""获取卫星 TLE GeoJSON 数据""" """获取卫星 TLE GeoJSON 数据"""
@@ -409,8 +413,9 @@ async def get_satellites_geojson(
.where(CollectedData.source == "celestrak_tle") .where(CollectedData.source == "celestrak_tle")
.where(CollectedData.name != "Unknown") .where(CollectedData.name != "Unknown")
.order_by(CollectedData.id.desc()) .order_by(CollectedData.id.desc())
.limit(limit)
) )
if limit is not None:
stmt = stmt.limit(limit)
result = await db.execute(stmt) result = await db.execute(stmt)
records = result.scalars().all() records = result.scalars().all()

View File

@@ -47,6 +47,32 @@ Released: 2026-03-26
- Older satellite records can still fall back to backend-generated TLE lines when raw lines are unavailable. - Older satellite records can still fall back to backend-generated TLE lines when raw lines are unavailable.
- This release is primarily focused on Earth module stability rather than visible admin UI changes. - This release is primarily focused on Earth module stability rather than visible admin UI changes.
## 0.21.0
Released: 2026-03-26
### Highlights
- Added legacy-inspired inertial drag behavior to the Earth big-screen module.
- Removed the hard 10,000-satellite ceiling when Earth satellite loading is configured as unlimited.
- Tightened Earth toolbar and hover-state synchronization for a more consistent runtime feel.
### Added
- Added inertial drag state and smoothing to the Earth runtime so drag release now decays naturally.
### Improved
- Improved drag handling so moving the pointer outside the canvas no longer prematurely stops rotation.
- Improved satellite loading to support dynamic frontend buffer sizing when no explicit limit is set.
- Improved Earth interaction fidelity by keeping the hover ring synchronized with moving satellites.
### Fixed
- Fixed the trails toolbar button so its default visual state matches the actual default runtime state.
- Fixed the satellite GeoJSON endpoint so omitting `limit` no longer silently falls back to `10000`.
- Fixed hover ring lag where the ring could stay behind the satellite until the next mouse move.
## 0.19.0 ## 0.19.0
Released: 2026-03-25 Released: 2026-03-25

View File

@@ -16,7 +16,7 @@
## Current Version ## Current Version
- `main` 当前主线历史推导到:`0.16.5` - `main` 当前主线历史推导到:`0.16.5`
- `dev` 当前开发分支历史推导到:`0.20.0` - `dev` 当前开发分支历史推导到:`0.21.0`
## Timeline ## Timeline
@@ -62,6 +62,7 @@
| `0.18.1` | bugfix | `dev` | `cc5f16f8` | fix settings layout and frontend startup checks | | `0.18.1` | bugfix | `dev` | `cc5f16f8` | fix settings layout and frontend startup checks |
| `0.19.0` | feature | `dev` | `020c1d50` | refine data management and collection workflows | | `0.19.0` | feature | `dev` | `020c1d50` | refine data management and collection workflows |
| `0.20.0` | feature | `dev` | `ce5feba3` | stabilize Earth module and fix satellite TLE handling | | `0.20.0` | feature | `dev` | `ce5feba3` | stabilize Earth module and fix satellite TLE handling |
| `0.21.0` | feature | `dev` | `pending` | add Earth inertial drag, sync hover/trail state, and support unlimited satellite loading |
## Maintenance Commits Not Counted as Version Bumps ## Maintenance Commits Not Counted as Version Bumps

View File

@@ -1,6 +1,6 @@
{ {
"name": "planet-frontend", "name": "planet-frontend",
"version": "0.20.0", "version": "0.21.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@ant-design/icons": "^5.2.6", "@ant-design/icons": "^5.2.6",

View File

@@ -50,7 +50,7 @@
<button id="toggle-cables" class="toolbar-btn active" title="显示/隐藏线缆">🌐<span class="tooltip">隐藏线缆</span></button> <button id="toggle-cables" class="toolbar-btn active" title="显示/隐藏线缆">🌐<span class="tooltip">隐藏线缆</span></button>
<button id="toggle-terrain" class="toolbar-btn" title="显示/隐藏地形">⛰️<span class="tooltip">显示/隐藏地形</span></button> <button id="toggle-terrain" class="toolbar-btn" title="显示/隐藏地形">⛰️<span class="tooltip">显示/隐藏地形</span></button>
<button id="toggle-satellites" class="toolbar-btn" title="显示/隐藏卫星">🛰️<span class="tooltip">显示卫星</span></button> <button id="toggle-satellites" class="toolbar-btn" title="显示/隐藏卫星">🛰️<span class="tooltip">显示卫星</span></button>
<button id="toggle-trails" class="toolbar-btn" title="显示/隐藏轨迹"><span class="tooltip">显示/隐藏轨迹</span></button> <button id="toggle-trails" class="toolbar-btn active" title="显示/隐藏轨迹"><span class="tooltip">隐藏轨迹</span></button>
<button id="reload-data" class="toolbar-btn" title="重新加载数据">🔃<span class="tooltip">重新加载数据</span></button> <button id="reload-data" class="toolbar-btn" title="重新加载数据">🔃<span class="tooltip">重新加载数据</span></button>
</div> </div>
<button id="toolbar-toggle" class="toolbar-btn" title="展开/收起工具栏"><span class="toggle-arrow"></span></button> <button id="toolbar-toggle" class="toolbar-btn" title="展开/收起工具栏"><span class="toggle-arrow"></span></button>

View File

@@ -54,7 +54,7 @@ export const CABLE_STATE = {
}; };
export const SATELLITE_CONFIG = { export const SATELLITE_CONFIG = {
maxCount: 10000, maxCount: -1,
trailLength: 10, trailLength: 10,
dotSize: 4, dotSize: 4,
ringSize: 0.07, ringSize: 0.07,

View File

@@ -293,6 +293,12 @@ function setupTerrainControls() {
const toolbarToggle = document.getElementById("toolbar-toggle"); const toolbarToggle = document.getElementById("toolbar-toggle");
const toolbar = document.getElementById("control-toolbar"); const toolbar = document.getElementById("control-toolbar");
if (trailsBtn) {
trailsBtn.classList.add("active");
const tooltip = trailsBtn.querySelector(".tooltip");
if (tooltip) tooltip.textContent = "隐藏轨迹";
}
bindListener(terrainBtn, "click", function () { bindListener(terrainBtn, "click", function () {
showTerrain = !showTerrain; showTerrain = !showTerrain;
toggleTerrain(showTerrain); toggleTerrain(showTerrain);

View File

@@ -81,6 +81,8 @@ export let renderer;
let simplex; let simplex;
let isDragging = false; let isDragging = false;
let previousMousePosition = { x: 0, y: 0 }; let previousMousePosition = { x: 0, y: 0 };
let targetRotation = { x: 0, y: 0 };
let inertialVelocity = { x: 0, y: 0 };
let hoveredCable = null; let hoveredCable = null;
let hoveredSatellite = null; let hoveredSatellite = null;
let hoveredSatelliteIndex = null; let hoveredSatelliteIndex = null;
@@ -108,6 +110,10 @@ const scratchCableCenter = new THREE.Vector3();
const scratchCableDirection = new THREE.Vector3(); const scratchCableDirection = new THREE.Vector3();
const cleanupFns = []; const cleanupFns = [];
const DRAG_ROTATION_FACTOR = 0.005;
const DRAG_SMOOTHING_FACTOR = 0.18;
const INERTIA_DAMPING = 0.92;
const INERTIA_MIN_VELOCITY = 0.00008;
function bindListener(target, eventName, handler, options) { function bindListener(target, eventName, handler, options) {
if (!target) return; if (!target) return;
@@ -327,6 +333,11 @@ export function init() {
addLights(); addLights();
initInfoCard(); initInfoCard();
const earthObj = createEarth(scene); const earthObj = createEarth(scene);
targetRotation = {
x: earthObj.rotation.x,
y: earthObj.rotation.y,
};
inertialVelocity = { x: 0, y: 0 };
createClouds(scene, earthObj); createClouds(scene, earthObj);
createTerrain(scene, earthObj, simplex); createTerrain(scene, earthObj, simplex);
createStars(scene); createStars(scene);
@@ -461,16 +472,17 @@ function setupEventListeners() {
const handleMouseMove = (event) => onMouseMove(event); const handleMouseMove = (event) => onMouseMove(event);
const handleMouseDown = (event) => onMouseDown(event); const handleMouseDown = (event) => onMouseDown(event);
const handleMouseUp = () => onMouseUp(); const handleMouseUp = () => onMouseUp();
const handleMouseLeave = () => onMouseLeave();
const handleClick = (event) => onClick(event); const handleClick = (event) => onClick(event);
const handlePageHide = () => destroy(); const handlePageHide = () => destroy();
bindListener(window, "resize", handleResize); bindListener(window, "resize", handleResize);
bindListener(window, "pagehide", handlePageHide); bindListener(window, "pagehide", handlePageHide);
bindListener(window, "beforeunload", handlePageHide); bindListener(window, "beforeunload", handlePageHide);
bindListener(renderer.domElement, "mousemove", handleMouseMove); bindListener(window, "mousemove", handleMouseMove);
bindListener(renderer.domElement, "mousedown", handleMouseDown); bindListener(renderer.domElement, "mousedown", handleMouseDown);
bindListener(renderer.domElement, "mouseup", handleMouseUp); bindListener(window, "mouseup", handleMouseUp);
bindListener(renderer.domElement, "mouseleave", handleMouseUp); bindListener(renderer.domElement, "mouseleave", handleMouseLeave);
bindListener(renderer.domElement, "click", handleClick); bindListener(renderer.domElement, "click", handleClick);
} }
@@ -512,8 +524,13 @@ function onMouseMove(event) {
const deltaX = event.clientX - previousMousePosition.x; const deltaX = event.clientX - previousMousePosition.x;
const deltaY = event.clientY - previousMousePosition.y; const deltaY = event.clientY - previousMousePosition.y;
earth.rotation.y += deltaX * 0.005; const rotationDeltaY = deltaX * DRAG_ROTATION_FACTOR;
earth.rotation.x += deltaY * 0.005; const rotationDeltaX = deltaY * DRAG_ROTATION_FACTOR;
targetRotation.y += rotationDeltaY;
targetRotation.x += rotationDeltaX;
inertialVelocity.y = rotationDeltaY;
inertialVelocity.x = rotationDeltaX;
previousMousePosition = { x: event.clientX, y: event.clientY }; previousMousePosition = { x: event.clientX, y: event.clientY };
hideTooltip(); hideTooltip();
return; return;
@@ -624,10 +641,18 @@ function onMouseMove(event) {
} }
function onMouseDown(event) { function onMouseDown(event) {
const earth = getEarth();
isDragging = true; isDragging = true;
dragStartTime = Date.now(); dragStartTime = Date.now();
isLongDrag = false; isLongDrag = false;
previousMousePosition = { x: event.clientX, y: event.clientY }; previousMousePosition = { x: event.clientX, y: event.clientY };
inertialVelocity = { x: 0, y: 0 };
if (earth) {
targetRotation = {
x: earth.rotation.x,
y: earth.rotation.y,
};
}
document.getElementById("container")?.classList.add("dragging"); document.getElementById("container")?.classList.add("dragging");
hideTooltip(); hideTooltip();
} }
@@ -637,6 +662,10 @@ function onMouseUp() {
document.getElementById("container")?.classList.remove("dragging"); document.getElementById("container")?.classList.remove("dragging");
} }
function onMouseLeave() {
hideTooltip();
}
function onClick(event) { function onClick(event) {
const earth = getEarth(); const earth = getEarth();
if (!earth) return; if (!earth) return;
@@ -735,6 +764,36 @@ function animate() {
if (getAutoRotate() && earth) { if (getAutoRotate() && earth) {
earth.rotation.y += CONFIG.rotationSpeed * (deltaTime / 16); earth.rotation.y += CONFIG.rotationSpeed * (deltaTime / 16);
targetRotation.y = earth.rotation.y;
targetRotation.x = earth.rotation.x;
}
if (earth) {
if (isDragging) {
// Smoothly follow the drag target to match the legacy interaction feel.
earth.rotation.x +=
(targetRotation.x - earth.rotation.x) * DRAG_SMOOTHING_FACTOR;
earth.rotation.y +=
(targetRotation.y - earth.rotation.y) * DRAG_SMOOTHING_FACTOR;
} else if (
Math.abs(inertialVelocity.x) > INERTIA_MIN_VELOCITY ||
Math.abs(inertialVelocity.y) > INERTIA_MIN_VELOCITY
) {
// Continue rotating after release and gradually decay the motion.
targetRotation.x += inertialVelocity.x * (deltaTime / 16);
targetRotation.y += inertialVelocity.y * (deltaTime / 16);
earth.rotation.x +=
(targetRotation.x - earth.rotation.x) * DRAG_SMOOTHING_FACTOR;
earth.rotation.y +=
(targetRotation.y - earth.rotation.y) * DRAG_SMOOTHING_FACTOR;
inertialVelocity.x *= Math.pow(INERTIA_DAMPING, deltaTime / 16);
inertialVelocity.y *= Math.pow(INERTIA_DAMPING, deltaTime / 16);
} else {
inertialVelocity.x = 0;
inertialVelocity.y = 0;
targetRotation.x = earth.rotation.x;
targetRotation.y = earth.rotation.y;
}
} }
applyCableVisualState(); applyCableVisualState();

View File

@@ -19,9 +19,10 @@ let earthObjRef = null;
let sceneRef = null; let sceneRef = null;
let cameraRef = null; let cameraRef = null;
let lockedSatelliteIndex = null; let lockedSatelliteIndex = null;
let hoveredSatelliteIndex = null;
let positionUpdateAccumulator = 0; let positionUpdateAccumulator = 0;
let satelliteCapacity = 0;
const MAX_SATELLITES = SATELLITE_CONFIG.maxCount;
const TRAIL_LENGTH = SATELLITE_CONFIG.trailLength; const TRAIL_LENGTH = SATELLITE_CONFIG.trailLength;
const DOT_TEXTURE_SIZE = 32; const DOT_TEXTURE_SIZE = 32;
const POSITION_UPDATE_INTERVAL_MS = 250; const POSITION_UPDATE_INTERVAL_MS = 250;
@@ -114,17 +115,9 @@ function createRingTexture(innerRadius, outerRadius, color = "#ffffff") {
export function createSatellites(scene, earthObj) { export function createSatellites(scene, earthObj) {
initSatelliteScene(scene, earthObj); initSatelliteScene(scene, earthObj);
const positions = new Float32Array(MAX_SATELLITES * 3);
const colors = new Float32Array(MAX_SATELLITES * 3);
const dotTexture = createDotTexture(); const dotTexture = createDotTexture();
const pointsGeometry = new THREE.BufferGeometry(); const pointsGeometry = new THREE.BufferGeometry();
pointsGeometry.setAttribute(
"position",
new THREE.BufferAttribute(positions, 3),
);
pointsGeometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
const pointsMaterial = new THREE.PointsMaterial({ const pointsMaterial = new THREE.PointsMaterial({
size: SATELLITE_CONFIG.dotSize, size: SATELLITE_CONFIG.dotSize,
@@ -159,18 +152,7 @@ export function createSatellites(scene, earthObj) {
earthObj.add(satellitePoints); earthObj.add(satellitePoints);
const trailPositions = new Float32Array(MAX_SATELLITES * TRAIL_LENGTH * 3);
const trailColors = new Float32Array(MAX_SATELLITES * TRAIL_LENGTH * 3);
const trailGeometry = new THREE.BufferGeometry(); const trailGeometry = new THREE.BufferGeometry();
trailGeometry.setAttribute(
"position",
new THREE.BufferAttribute(trailPositions, 3),
);
trailGeometry.setAttribute(
"color",
new THREE.BufferAttribute(trailColors, 3),
);
const trailMaterial = new THREE.LineBasicMaterial({ const trailMaterial = new THREE.LineBasicMaterial({
vertexColors: true, vertexColors: true,
@@ -183,16 +165,59 @@ export function createSatellites(scene, earthObj) {
satelliteTrails.visible = false; satelliteTrails.visible = false;
satelliteTrails.userData = { type: "satelliteTrails" }; satelliteTrails.userData = { type: "satelliteTrails" };
earthObj.add(satelliteTrails); earthObj.add(satelliteTrails);
ensureSatelliteCapacity(0);
satellitePositions = Array.from({ length: MAX_SATELLITES }, () => ({ positionUpdateAccumulator = POSITION_UPDATE_INTERVAL_MS;
return satellitePoints;
}
function getRequestedSatelliteLimit() {
return SATELLITE_CONFIG.maxCount < 0 ? null : SATELLITE_CONFIG.maxCount;
}
function createSatellitePositionState() {
return {
current: new THREE.Vector3(), current: new THREE.Vector3(),
trail: [], trail: [],
trailIndex: 0, trailIndex: 0,
trailCount: 0, trailCount: 0,
})); };
}
positionUpdateAccumulator = POSITION_UPDATE_INTERVAL_MS; function ensureSatelliteCapacity(count) {
return satellitePoints; if (!satellitePoints || !satelliteTrails) return;
const nextCapacity = Math.max(count, 0);
if (nextCapacity === satelliteCapacity) return;
const positions = new Float32Array(nextCapacity * 3);
const colors = new Float32Array(nextCapacity * 3);
satellitePoints.geometry.setAttribute(
"position",
new THREE.BufferAttribute(positions, 3),
);
satellitePoints.geometry.setAttribute(
"color",
new THREE.BufferAttribute(colors, 3),
);
satellitePoints.geometry.setDrawRange(0, 0);
const trailPositions = new Float32Array(nextCapacity * TRAIL_LENGTH * 3);
const trailColors = new Float32Array(nextCapacity * TRAIL_LENGTH * 3);
satelliteTrails.geometry.setAttribute(
"position",
new THREE.BufferAttribute(trailPositions, 3),
);
satelliteTrails.geometry.setAttribute(
"color",
new THREE.BufferAttribute(trailColors, 3),
);
satellitePositions = Array.from(
{ length: nextCapacity },
createSatellitePositionState,
);
satelliteCapacity = nextCapacity;
} }
function computeSatellitePosition(satellite, time) { function computeSatellitePosition(satellite, time) {
@@ -357,15 +382,20 @@ function generateFallbackPosition(satellite, index, total) {
} }
export async function loadSatellites() { export async function loadSatellites() {
const response = await fetch( const limit = getRequestedSatelliteLimit();
`${SATELLITE_CONFIG.apiPath}?limit=${SATELLITE_CONFIG.maxCount}`, const url = new URL(SATELLITE_CONFIG.apiPath, window.location.origin);
); if (limit !== null) {
url.searchParams.set("limit", String(limit));
}
const response = await fetch(url.toString());
if (!response.ok) { if (!response.ok) {
throw new Error(`卫星接口返回 HTTP ${response.status}`); throw new Error(`卫星接口返回 HTTP ${response.status}`);
} }
const data = await response.json(); const data = await response.json();
satelliteData = data.features || []; satelliteData = data.features || [];
ensureSatelliteCapacity(satelliteData.length);
positionUpdateAccumulator = POSITION_UPDATE_INTERVAL_MS; positionUpdateAccumulator = POSITION_UPDATE_INTERVAL_MS;
return satelliteData.length; return satelliteData.length;
} }
@@ -392,7 +422,7 @@ export function updateSatellitePositions(deltaTime = 0, force = false) {
const trailPositions = satelliteTrails.geometry.attributes.position.array; const trailPositions = satelliteTrails.geometry.attributes.position.array;
const trailColors = satelliteTrails.geometry.attributes.color.array; const trailColors = satelliteTrails.geometry.attributes.color.array;
const baseTime = new Date(Date.now() + elapsedMs); const baseTime = new Date(Date.now() + elapsedMs);
const count = Math.min(satelliteData.length, MAX_SATELLITES); const count = Math.min(satelliteData.length, satelliteCapacity);
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const satellite = satelliteData[i]; const satellite = satelliteData[i];
@@ -485,7 +515,7 @@ export function updateSatellitePositions(deltaTime = 0, force = false) {
} }
} }
for (let i = count; i < MAX_SATELLITES; i++) { for (let i = count; i < satelliteCapacity; i++) {
positions[i * 3] = 0; positions[i * 3] = 0;
positions[i * 3 + 1] = 0; positions[i * 3 + 1] = 0;
positions[i * 3 + 2] = 0; positions[i * 3 + 2] = 0;
@@ -504,6 +534,17 @@ export function updateSatellitePositions(deltaTime = 0, force = false) {
satelliteTrails.geometry.attributes.position.needsUpdate = true; satelliteTrails.geometry.attributes.position.needsUpdate = true;
satelliteTrails.geometry.attributes.color.needsUpdate = true; satelliteTrails.geometry.attributes.color.needsUpdate = true;
// Keep the hover ring synced with the propagated satellite position even
// when the pointer stays still and no new hover event is emitted.
if (
hoveredSatelliteIndex !== null &&
hoveredSatelliteIndex >= 0 &&
hoveredSatelliteIndex < count &&
hoveredSatelliteIndex !== lockedSatelliteIndex
) {
updateHoverRingPosition(satellitePositions[hoveredSatelliteIndex].current);
}
} }
export function toggleSatellites(visible) { export function toggleSatellites(visible) {
@@ -563,6 +604,10 @@ export function setLockedSatelliteIndex(index) {
lockedSatelliteIndex = index; lockedSatelliteIndex = index;
} }
export function setHoveredSatelliteIndex(index) {
hoveredSatelliteIndex = index;
}
export function isSatelliteFrontFacing(index, camera = cameraRef) { export function isSatelliteFrontFacing(index, camera = cameraRef) {
if (!earthObjRef || !camera) return true; if (!earthObjRef || !camera) return true;
if (!satellitePositions || !satellitePositions[index]) return true; if (!satellitePositions || !satellitePositions[index]) return true;
@@ -718,14 +763,17 @@ export function updateHoverRingPosition(position) {
export function setSatelliteRingState(index, state, position) { export function setSatelliteRingState(index, state, position) {
switch (state) { switch (state) {
case "hover": case "hover":
hoveredSatelliteIndex = index;
hideHoverRings(); hideHoverRings();
showHoverRing(position, false); showHoverRing(position, false);
break; break;
case "locked": case "locked":
hoveredSatelliteIndex = null;
hideHoverRings(); hideHoverRings();
showHoverRing(position, true); showHoverRing(position, true);
break; break;
case "none": case "none":
hoveredSatelliteIndex = null;
hideHoverRings(); hideHoverRings();
hideLockedRing(); hideLockedRing();
break; break;
@@ -826,6 +874,7 @@ export function clearSatelliteData() {
satelliteData = []; satelliteData = [];
selectedSatellite = null; selectedSatellite = null;
lockedSatelliteIndex = null; lockedSatelliteIndex = null;
hoveredSatelliteIndex = null;
positionUpdateAccumulator = 0; positionUpdateAccumulator = 0;
satellitePositions.forEach((position) => { satellitePositions.forEach((position) => {
@@ -836,22 +885,30 @@ export function clearSatelliteData() {
}); });
if (satellitePoints) { if (satellitePoints) {
const positions = satellitePoints.geometry.attributes.position.array; const positionAttr = satellitePoints.geometry.attributes.position;
const colors = satellitePoints.geometry.attributes.color.array; const colorAttr = satellitePoints.geometry.attributes.color;
positions.fill(0); if (positionAttr?.array) {
colors.fill(0); positionAttr.array.fill(0);
satellitePoints.geometry.attributes.position.needsUpdate = true; positionAttr.needsUpdate = true;
satellitePoints.geometry.attributes.color.needsUpdate = true; }
if (colorAttr?.array) {
colorAttr.array.fill(0);
colorAttr.needsUpdate = true;
}
satellitePoints.geometry.setDrawRange(0, 0); satellitePoints.geometry.setDrawRange(0, 0);
} }
if (satelliteTrails) { if (satelliteTrails) {
const trailPositions = satelliteTrails.geometry.attributes.position.array; const trailPositionAttr = satelliteTrails.geometry.attributes.position;
const trailColors = satelliteTrails.geometry.attributes.color.array; const trailColorAttr = satelliteTrails.geometry.attributes.color;
trailPositions.fill(0); if (trailPositionAttr?.array) {
trailColors.fill(0); trailPositionAttr.array.fill(0);
satelliteTrails.geometry.attributes.position.needsUpdate = true; trailPositionAttr.needsUpdate = true;
satelliteTrails.geometry.attributes.color.needsUpdate = true; }
if (trailColorAttr?.array) {
trailColorAttr.array.fill(0);
trailColorAttr.needsUpdate = true;
}
} }
hideHoverRings(); hideHoverRings();
@@ -873,6 +930,7 @@ export function resetSatelliteState() {
} }
satellitePositions = []; satellitePositions = [];
satelliteCapacity = 0;
showSatellites = false; showSatellites = false;
showTrails = true; showTrails = true;
} }

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "planet" name = "planet"
version = "0.20.0" version = "0.21.0"
description = "智能星球计划 - 态势感知系统" description = "智能星球计划 - 态势感知系统"
requires-python = ">=3.14" requires-python = ">=3.14"
dependencies = [ dependencies = [

2
uv.lock generated
View File

@@ -475,7 +475,7 @@ wheels = [
[[package]] [[package]]
name = "planet" name = "planet"
version = "0.20.0" version = "0.21.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "aiofiles" }, { name = "aiofiles" },