release: bump version to 0.40.1

This commit is contained in:
linkong
2026-04-24 17:28:03 +08:00
parent 86807f6af6
commit e9464a9833
8 changed files with 115 additions and 41 deletions

View File

@@ -1 +1 @@
0.40.0 0.40.1

View File

@@ -10,6 +10,16 @@ This project follows the repository versioning rule:
## [0.39.0] — 2026-04-24 ## [0.39.0] — 2026-04-24
## [0.40.1] — 2026-04-24
### 🔧 Improvements
- 卫星选中标记lockedring / lockeddot / 光晕)颜色统一跟随图例轨道倾角分类配色
- 修复 Starlink footprint 在特定视角下遮蔽卫星点的渲染顺序问题Group renderOrder 影响子 Mesh 排序)
- footprint 材质改为 `depthTest: false` + 相机朝向 limbFade替代 polygonOffset 深度竞争方案
- 修复选中海缆时误触发附近卫星高亮(该行为属于 BGP 事件点逻辑,不应用于海缆)
---
## [0.40.0] — 2026-04-24 ## [0.40.0] — 2026-04-24
### ✨ Highlights ### ✨ Highlights

View File

@@ -16,12 +16,13 @@
## Current Version ## Current Version
- `main` 当前主线历史推导到:`0.16.5` - `main` 当前主线历史推导到:`0.16.5`
- `dev` 当前开发分支历史推导到:`0.40.0` - `dev` 当前开发分支历史推导到:`0.40.1`
## Timeline ## Timeline
| Version | Type | Branch | Commit | Summary | | Version | Type | Branch | Commit | Summary |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| `0.40.1` | improvement | `dev` | `pending` | 卫星选中标记配色跟随图例,修复 footprint 遮蔽卫星渲染问题,修复选中海缆误触发卫星高亮 |
| `0.40.0` | feature | `dev` | `pending` | Earth 卫星 footprint 按星座能力分层Iridium 独立 coverage ring 落地,卫星详情卡补齐覆盖能力与当前显示说明 | | `0.40.0` | feature | `dev` | `pending` | Earth 卫星 footprint 按星座能力分层Iridium 独立 coverage ring 落地,卫星详情卡补齐覆盖能力与当前显示说明 |
| `0.39.0` | feature | `dev` | `pending` | 后端统一结构化日志地基落地,系统日志页重构为紧凑日志工作台,并修复 Earth 移动端态势抽屉与新闻详情同步问题 | | `0.39.0` | feature | `dev` | `pending` | 后端统一结构化日志地基落地,系统日志页重构为紧凑日志工作台,并修复 Earth 移动端态势抽屉与新闻详情同步问题 |
| `0.38.0` | feature | `dev` | `pending` | Earth 新闻接入通用巡航与专用卡片链路,系统日志页升级为结构化时间/级别过滤与真正字符串检索 | | `0.38.0` | feature | `dev` | `pending` | Earth 新闻接入通用巡航与专用卡片链路,系统日志页升级为结构化时间/级别过滤与真正字符串检索 |

View File

@@ -1,6 +1,6 @@
{ {
"name": "planet-frontend", "name": "planet-frontend",
"version": "0.40.0", "version": "0.40.1",
"private": true, "private": true,
"packageManager": "bun@1", "packageManager": "bun@1",
"dependencies": { "dependencies": {

View File

@@ -96,6 +96,7 @@ import {
setSatelliteCamera, setSatelliteCamera,
setSatelliteSunDirection, setSatelliteSunDirection,
setLockedSatelliteIndex, setLockedSatelliteIndex,
setHoveredSatelliteIndex,
resetSatelliteState, resetSatelliteState,
clearSatelliteData, clearSatelliteData,
} from "./satellites.js"; } from "./satellites.js";
@@ -482,6 +483,7 @@ function clearTransientHoverState() {
} }
hoveredSatellite = null; hoveredSatellite = null;
hoveredSatelliteIndex = null; hoveredSatelliteIndex = null;
setHoveredSatelliteIndex(null);
} }
function applyBGPHoverState(marker) { function applyBGPHoverState(marker) {
@@ -933,6 +935,9 @@ async function focusSearchSatellite(index) {
const satPositions = getSatellitePositions(); const satPositions = getSatellitePositions();
if (satPositions?.[index]) { if (satPositions?.[index]) {
setSatelliteRingState(index, "locked", satPositions[index].current); setSatelliteRingState(index, "locked", satPositions[index].current);
if (hoveredSatelliteIndex === index) {
setHoveredSatelliteIndex(index);
}
} }
showSatelliteInfo(sat.properties, getSearchCardCoords()); showSatelliteInfo(sat.properties, getSearchCardCoords());
showStatusMessage(`已定位卫星:${sat.properties.name || sat.properties.norad_cat_id || "未知卫星"}`, "info"); showStatusMessage(`已定位卫星:${sat.properties.name || sat.properties.norad_cat_id || "未知卫星"}`, "info");
@@ -2663,6 +2668,7 @@ function onMouseMove(event) {
); );
} }
} }
setHoveredSatelliteIndex(hoveredSatelliteIndex);
showTooltip(event.clientX + TOOLTIP_CURSOR_OFFSET, event.clientY + TOOLTIP_CURSOR_OFFSET, getSatelliteBriefHtml(hoveredSat.properties)); showTooltip(event.clientX + TOOLTIP_CURSOR_OFFSET, event.clientY + TOOLTIP_CURSOR_OFFSET, getSatelliteBriefHtml(hoveredSat.properties));
objectTooltipShown = true; objectTooltipShown = true;
} else if (lockedObjectType === "bgp" && lockedObject) { } else if (lockedObjectType === "bgp" && lockedObject) {
@@ -2962,19 +2968,6 @@ function onClick(event) {
lockedObject = clickedCable; lockedObject = clickedCable;
lockedObjectType = "cable"; lockedObjectType = "cable";
setAutoRotate(false); setAutoRotate(false);
{
const cableLandingRegions = getLandingPoints()
.filter((lp) => lp.userData.cableNames?.includes(clickedCable.userData.name))
.map((lp) => {
const { lat, lon } = vector3ToLatLon(lp.position);
return { latitude: lat, longitude: lon };
});
const relatedSatelliteIndices = getRelatedSatelliteIndicesForRegions(
cableLandingRegions,
{ limit: 6, maxAngleDeg: 20 },
);
highlightRelatedSatellites(relatedSatelliteIndices, RELATED_SATELLITE_HIGHLIGHT_COLOR);
}
handleCableClick(clickedCable); handleCableClick(clickedCable);
showCableInfo(clickedCable, { x: event.clientX, y: event.clientY }); showCableInfo(clickedCable, { x: event.clientX, y: event.clientY });
return; return;
@@ -3029,6 +3022,9 @@ function onClick(event) {
"locked", "locked",
satPositions[selectedIndex].current, satPositions[selectedIndex].current,
); );
if (hoveredSatelliteIndex === selectedIndex) {
setHoveredSatelliteIndex(selectedIndex);
}
} }
showSatelliteInfo(sat.properties, { x: event.clientX, y: event.clientY }); showSatelliteInfo(sat.properties, { x: event.clientX, y: event.clientY });

View File

@@ -79,6 +79,9 @@ const LOCKED_HALO_BASE_OPACITY = 0.54;
const LOCKED_HALO_OFFSET = 0.0014; const LOCKED_HALO_OFFSET = 0.0014;
const LOCKED_HALO_CORE_PIXEL_RADIUS = 8; const LOCKED_HALO_CORE_PIXEL_RADIUS = 8;
const LOCKED_HALO_PIXEL_RADIUS = 24; const LOCKED_HALO_PIXEL_RADIUS = 24;
const FILLED_TEXTURE_RADIUS_RATIO = 0.28;
const LOCKED_RING_IDLE_SCALE = 0.68;
const LOCKED_RING_IDLE_OPACITY = 0.92;
const EARTH_RADIUS_KM = 6378.137; const EARTH_RADIUS_KM = 6378.137;
const GROUND_FOOTPRINT_MIN_ELEVATION_DEG = 25; const GROUND_FOOTPRINT_MIN_ELEVATION_DEG = 25;
const GROUND_FOOTPRINT_RADIUS_OFFSET = 0.72; const GROUND_FOOTPRINT_RADIUS_OFFSET = 0.72;
@@ -345,6 +348,25 @@ function createRingTexture(innerRadius, outerRadius, color = "#ffffff") {
return texture; return texture;
} }
function createFilledCircleTexture(color = "#ffcc00") {
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 radius = size * FILLED_TEXTURE_RADIUS_RATIO;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(center, center, radius, 0, Math.PI * 2);
ctx.fill();
const texture = new THREE.CanvasTexture(canvas);
texture.needsUpdate = true;
return texture;
}
export function createSatellites(scene, earthObj) { export function createSatellites(scene, earthObj) {
initSatelliteScene(scene, earthObj); initSatelliteScene(scene, earthObj);
const dotTexture = createDotTexture(); const dotTexture = createDotTexture();
@@ -1156,14 +1178,14 @@ export function isSatelliteFrontFacing(index, camera = cameraRef) {
); );
} }
function createLockedHaloMaterial() { function createLockedHaloMaterial(color = "#ffbf47") {
return new THREE.ShaderMaterial({ return new THREE.ShaderMaterial({
transparent: true, transparent: true,
depthTest: false, depthTest: false,
depthWrite: false, depthWrite: false,
side: THREE.DoubleSide, side: THREE.DoubleSide,
uniforms: { uniforms: {
uColor: { value: new THREE.Color(0xffbf47) }, uColor: { value: new THREE.Color(color) },
uOpacity: { value: LOCKED_HALO_BASE_OPACITY }, uOpacity: { value: LOCKED_HALO_BASE_OPACITY },
}, },
vertexShader: ` vertexShader: `
@@ -1196,10 +1218,8 @@ function createGroundFootprintMaterial() {
return new THREE.ShaderMaterial({ return new THREE.ShaderMaterial({
transparent: true, transparent: true,
side: THREE.DoubleSide, side: THREE.DoubleSide,
depthTest: false,
depthWrite: false, depthWrite: false,
polygonOffset: true,
polygonOffsetFactor: -2,
polygonOffsetUnits: -2,
uniforms: { uniforms: {
uColor: { value: new THREE.Color(0xffffff) }, uColor: { value: new THREE.Color(0xffffff) },
uOpacity: { value: 0.46 }, uOpacity: { value: 0.46 },
@@ -1288,6 +1308,12 @@ function createGroundFootprintMaterial() {
} }
vec3 worldNormal = normalize(vWorldPosition); vec3 worldNormal = normalize(vWorldPosition);
vec3 toCam = normalize(cameraPosition - vWorldPosition);
float facing = dot(worldNormal, toCam);
float limbFade = smoothstep(-0.08, 0.12, facing);
if (limbFade <= 0.0) discard;
alpha *= limbFade;
vec3 sunDir = normalize(uSunDirectionWorld); vec3 sunDir = normalize(uSunDirectionWorld);
float sunFacing = dot(worldNormal, sunDir); float sunFacing = dot(worldNormal, sunDir);
float daylight = clamp(sunFacing * 0.5 + 0.5, 0.0, 1.0); float daylight = clamp(sunFacing * 0.5 + 0.5, 0.0, 1.0);
@@ -1720,13 +1746,13 @@ function updateGroundFootprintTransform(position) {
} }
} }
function showSelfGlowStyle(position) { function showSelfGlowStyle(position, color = "#ffd25a") {
const dotGeometry = new THREE.CircleGeometry( const dotGeometry = new THREE.CircleGeometry(
LOCKED_HALO_CORE_RADIUS, LOCKED_HALO_CORE_RADIUS,
LOCKED_HALO_CORE_SEGMENTS, LOCKED_HALO_CORE_SEGMENTS,
); );
const dotMaterial = new THREE.MeshBasicMaterial({ const dotMaterial = new THREE.MeshBasicMaterial({
color: 0xffd25a, color: new THREE.Color(color),
transparent: true, transparent: true,
opacity: 0.96, opacity: 0.96,
depthTest: false, depthTest: false,
@@ -1740,7 +1766,7 @@ function showSelfGlowStyle(position) {
lockedHaloMesh = new THREE.Mesh( lockedHaloMesh = new THREE.Mesh(
new THREE.CircleGeometry(LOCKED_HALO_RADIUS, 64), new THREE.CircleGeometry(LOCKED_HALO_RADIUS, 64),
createLockedHaloMaterial(), createLockedHaloMaterial(color),
); );
lockedHaloMesh.renderOrder = SATELLITE_CONFIG.overlayRenderOrder + 1; lockedHaloMesh.renderOrder = SATELLITE_CONFIG.overlayRenderOrder + 1;
sceneRef?.add(lockedHaloMesh); sceneRef?.add(lockedHaloMesh);
@@ -1750,7 +1776,7 @@ function showSelfGlowStyle(position) {
function showGroundFootprintStyle(position) { function showGroundFootprintStyle(position) {
if (!earthObjRef) return; if (!earthObjRef) return;
lockedGroundFootprintMesh = new THREE.Group(); lockedGroundFootprintMesh = new THREE.Group();
lockedGroundFootprintMesh.renderOrder = SATELLITE_CONFIG.overlayRenderOrder - 1; lockedGroundFootprintMesh.renderOrder = 0;
const fill = new THREE.Mesh( const fill = new THREE.Mesh(
new THREE.BufferGeometry(), new THREE.BufferGeometry(),
createGroundFootprintMaterial(), createGroundFootprintMaterial(),
@@ -1766,7 +1792,7 @@ function showIridiumReservedStyle(position) {
lockedIridiumFootprintMesh = createIridiumFootprintAdapter({ lockedIridiumFootprintMesh = createIridiumFootprintAdapter({
earthObj: earthObjRef, earthObj: earthObjRef,
earthRadiusWorld: CONFIG.earthRadius, earthRadiusWorld: CONFIG.earthRadius,
renderOrder: SATELLITE_CONFIG.overlayRenderOrder - 1, renderOrder: 0,
}); });
updateIridiumReservedStyle(position); updateIridiumReservedStyle(position);
} }
@@ -1782,14 +1808,17 @@ function updateIridiumReservedStyle(position) {
}); });
} }
function createRingSprite(position, isLocked = false) { function createRingSprite(position, isLocked = false, color = "#ffcc00") {
if (!earthObjRef) return null; if (!earthObjRef) return null;
const ringTexture = createRingTexture( const ringTexture = createRingTexture(
8, 8,
12, 12,
isLocked ? "#ffcc00" : "#ffffff", isLocked ? color : "#ffffff",
); );
const filledTexture = isLocked
? createFilledCircleTexture(color)
: null;
const spriteMaterial = new THREE.SpriteMaterial({ const spriteMaterial = new THREE.SpriteMaterial({
map: ringTexture, map: ringTexture,
transparent: true, transparent: true,
@@ -1799,6 +1828,8 @@ function createRingSprite(position, isLocked = false) {
}); });
const sprite = new THREE.Sprite(spriteMaterial); const sprite = new THREE.Sprite(spriteMaterial);
sprite.userData.ringTexture = ringTexture;
sprite.userData.filledTexture = filledTexture;
sprite.position.copy(position); sprite.position.copy(position);
sprite.scale.set(SATELLITE_CONFIG.ringSize, SATELLITE_CONFIG.ringSize, 1); sprite.scale.set(SATELLITE_CONFIG.ringSize, SATELLITE_CONFIG.ringSize, 1);
sprite.renderOrder = SATELLITE_CONFIG.overlayRenderOrder; sprite.renderOrder = SATELLITE_CONFIG.overlayRenderOrder;
@@ -1806,6 +1837,26 @@ function createRingSprite(position, isLocked = false) {
return sprite; return sprite;
} }
function isLockedSatelliteHovered() {
return (
lockedSatelliteIndex !== null &&
hoveredSatelliteIndex !== null &&
hoveredSatelliteIndex === lockedSatelliteIndex
);
}
function updateLockedMarkerVisual(isHovered) {
if (!lockedRingSprite?.material) return;
const nextTexture = isHovered
? lockedRingSprite.userData.ringTexture
: lockedRingSprite.userData.filledTexture || lockedRingSprite.userData.ringTexture;
if (lockedRingSprite.material.map !== nextTexture) {
lockedRingSprite.material.map = nextTexture;
lockedRingSprite.material.needsUpdate = true;
}
}
function createRelatedSatelliteSprite(position, color = "#7dd3fc") { function createRelatedSatelliteSprite(position, color = "#7dd3fc") {
if (!earthObjRef) return null; if (!earthObjRef) return null;
@@ -1830,11 +1881,12 @@ export function showHoverRing(position, isLocked = false) {
if (!earthObjRef || !position) return null; if (!earthObjRef || !position) return null;
if (isLocked) { if (isLocked) {
const lockedProps = getLockedSatelliteProperties() || {};
const legendColor = getSatelliteLegendRule(lockedProps).color;
hideLockedRing(); hideLockedRing();
lockedRingSprite = createRingSprite(position, true); lockedRingSprite = createRingSprite(position, true, legendColor);
const presentationMode = getSatellitePresentationMode( updateLockedMarkerVisual(isLockedSatelliteHovered());
getLockedSatelliteProperties() || {}, const presentationMode = getSatellitePresentationMode(lockedProps);
);
if ( if (
presentationMode === presentationMode ===
SATELLITE_PRESENTATION_MODES.STARLINK_GROUND_FOOTPRINT SATELLITE_PRESENTATION_MODES.STARLINK_GROUND_FOOTPRINT
@@ -1845,7 +1897,7 @@ export function showHoverRing(position, isLocked = false) {
) { ) {
showIridiumReservedStyle(position); showIridiumReservedStyle(position);
} else { } else {
showSelfGlowStyle(position); showSelfGlowStyle(position, legendColor);
} }
return lockedRingSprite; return lockedRingSprite;
} }
@@ -1864,7 +1916,15 @@ export function hideHoverRings() {
export function hideLockedRing() { export function hideLockedRing() {
if (lockedRingSprite) { if (lockedRingSprite) {
const ringTexture = lockedRingSprite.userData?.ringTexture;
const filledTexture = lockedRingSprite.userData?.filledTexture;
disposeObject3D(lockedRingSprite); disposeObject3D(lockedRingSprite);
if (ringTexture && ringTexture !== lockedRingSprite.material?.map) {
ringTexture.dispose();
}
if (filledTexture && filledTexture !== lockedRingSprite.material?.map) {
filledTexture.dispose();
}
lockedRingSprite = null; lockedRingSprite = null;
} }
clearLockedSatelliteStyleVisuals(); clearLockedSatelliteStyleVisuals();
@@ -1885,21 +1945,28 @@ export function updateLockedRingPosition(position) {
showHoverRing(position, true); showHoverRing(position, true);
} }
if (lockedRingSprite) { if (lockedRingSprite) {
const isHovered = isLockedSatelliteHovered();
updateLockedMarkerVisual(isHovered);
lockedRingSprite.position.copy(position); lockedRingSprite.position.copy(position);
const ringPulse = getBreathingPulse(breathingPhase); const ringPulse = getBreathingPulse(breathingPhase);
const breathScale = const breathScale =
1 + 1 +
(ringPulse * 2 - 1) * SATELLITE_CONFIG.breathingScaleAmplitude; (ringPulse * 2 - 1) * SATELLITE_CONFIG.breathingScaleAmplitude;
const markerSize = isHovered
? SATELLITE_CONFIG.ringSize
: SATELLITE_CONFIG.ringSize * LOCKED_RING_IDLE_SCALE;
lockedRingSprite.scale.set( lockedRingSprite.scale.set(
SATELLITE_CONFIG.ringSize * breathScale, markerSize * breathScale,
SATELLITE_CONFIG.ringSize * breathScale, markerSize * breathScale,
1, 1,
); );
lockedRingSprite.material.opacity = lockedRingSprite.material.opacity =
SATELLITE_CONFIG.breathingOpacityMin + isHovered
? SATELLITE_CONFIG.breathingOpacityMin +
ringPulse * ringPulse *
(SATELLITE_CONFIG.breathingOpacityMax - (SATELLITE_CONFIG.breathingOpacityMax -
SATELLITE_CONFIG.breathingOpacityMin); SATELLITE_CONFIG.breathingOpacityMin)
: LOCKED_RING_IDLE_OPACITY;
} }
if (lockedDotSprite) { if (lockedDotSprite) {

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "planet" name = "planet"
version = "0.40.0" version = "0.40.1"
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.40.0" version = "0.40.1"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "aiofiles" }, { name = "aiofiles" },