Enhance Earth interaction and bump version to 0.21.0
This commit is contained in:
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
6
frontend/public/earth/js/controls.js
vendored
6
frontend/public/earth/js/controls.js
vendored
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = [
|
||||||
|
|||||||
Reference in New Issue
Block a user