From e9464a9833fcbac74b7a2d7b63a1df6fc61c6aaa Mon Sep 17 00:00:00 2001 From: linkong Date: Fri, 24 Apr 2026 17:28:03 +0800 Subject: [PATCH] release: bump version to 0.40.1 --- VERSION | 2 +- docs/CHANGELOG.md | 10 +++ docs/version-history.md | 3 +- frontend/package.json | 2 +- frontend/public/earth/js/main.js | 22 ++--- frontend/public/earth/js/satellites.js | 113 ++++++++++++++++++++----- pyproject.toml | 2 +- uv.lock | 2 +- 8 files changed, 115 insertions(+), 41 deletions(-) diff --git a/VERSION b/VERSION index 9b0025a7..4f14f0cd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.40.0 +0.40.1 \ No newline at end of file diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 48d4908d..137a0aaf 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -10,6 +10,16 @@ This project follows the repository versioning rule: ## [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 ### ✨ Highlights diff --git a/docs/version-history.md b/docs/version-history.md index d4a794d3..a4a2893a 100644 --- a/docs/version-history.md +++ b/docs/version-history.md @@ -16,12 +16,13 @@ ## Current Version - `main` 当前主线历史推导到:`0.16.5` -- `dev` 当前开发分支历史推导到:`0.40.0` +- `dev` 当前开发分支历史推导到:`0.40.1` ## Timeline | Version | Type | Branch | Commit | Summary | | --- | --- | --- | --- | --- | +| `0.40.1` | improvement | `dev` | `pending` | 卫星选中标记配色跟随图例,修复 footprint 遮蔽卫星渲染问题,修复选中海缆误触发卫星高亮 | | `0.40.0` | feature | `dev` | `pending` | Earth 卫星 footprint 按星座能力分层,Iridium 独立 coverage ring 落地,卫星详情卡补齐覆盖能力与当前显示说明 | | `0.39.0` | feature | `dev` | `pending` | 后端统一结构化日志地基落地,系统日志页重构为紧凑日志工作台,并修复 Earth 移动端态势抽屉与新闻详情同步问题 | | `0.38.0` | feature | `dev` | `pending` | Earth 新闻接入通用巡航与专用卡片链路,系统日志页升级为结构化时间/级别过滤与真正字符串检索 | diff --git a/frontend/package.json b/frontend/package.json index faaeadf0..431d7e83 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "planet-frontend", - "version": "0.40.0", + "version": "0.40.1", "private": true, "packageManager": "bun@1", "dependencies": { diff --git a/frontend/public/earth/js/main.js b/frontend/public/earth/js/main.js index 096252c0..8fb8274b 100644 --- a/frontend/public/earth/js/main.js +++ b/frontend/public/earth/js/main.js @@ -96,6 +96,7 @@ import { setSatelliteCamera, setSatelliteSunDirection, setLockedSatelliteIndex, + setHoveredSatelliteIndex, resetSatelliteState, clearSatelliteData, } from "./satellites.js"; @@ -482,6 +483,7 @@ function clearTransientHoverState() { } hoveredSatellite = null; hoveredSatelliteIndex = null; + setHoveredSatelliteIndex(null); } function applyBGPHoverState(marker) { @@ -933,6 +935,9 @@ async function focusSearchSatellite(index) { const satPositions = getSatellitePositions(); if (satPositions?.[index]) { setSatelliteRingState(index, "locked", satPositions[index].current); + if (hoveredSatelliteIndex === index) { + setHoveredSatelliteIndex(index); + } } showSatelliteInfo(sat.properties, getSearchCardCoords()); 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)); objectTooltipShown = true; } else if (lockedObjectType === "bgp" && lockedObject) { @@ -2962,19 +2968,6 @@ function onClick(event) { lockedObject = clickedCable; lockedObjectType = "cable"; 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); showCableInfo(clickedCable, { x: event.clientX, y: event.clientY }); return; @@ -3029,6 +3022,9 @@ function onClick(event) { "locked", satPositions[selectedIndex].current, ); + if (hoveredSatelliteIndex === selectedIndex) { + setHoveredSatelliteIndex(selectedIndex); + } } showSatelliteInfo(sat.properties, { x: event.clientX, y: event.clientY }); diff --git a/frontend/public/earth/js/satellites.js b/frontend/public/earth/js/satellites.js index bf8edd7c..457395ff 100644 --- a/frontend/public/earth/js/satellites.js +++ b/frontend/public/earth/js/satellites.js @@ -79,6 +79,9 @@ const LOCKED_HALO_BASE_OPACITY = 0.54; const LOCKED_HALO_OFFSET = 0.0014; const LOCKED_HALO_CORE_PIXEL_RADIUS = 8; 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 GROUND_FOOTPRINT_MIN_ELEVATION_DEG = 25; const GROUND_FOOTPRINT_RADIUS_OFFSET = 0.72; @@ -345,6 +348,25 @@ function createRingTexture(innerRadius, outerRadius, color = "#ffffff") { 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) { initSatelliteScene(scene, earthObj); const dotTexture = createDotTexture(); @@ -1156,14 +1178,14 @@ export function isSatelliteFrontFacing(index, camera = cameraRef) { ); } -function createLockedHaloMaterial() { +function createLockedHaloMaterial(color = "#ffbf47") { return new THREE.ShaderMaterial({ transparent: true, depthTest: false, depthWrite: false, side: THREE.DoubleSide, uniforms: { - uColor: { value: new THREE.Color(0xffbf47) }, + uColor: { value: new THREE.Color(color) }, uOpacity: { value: LOCKED_HALO_BASE_OPACITY }, }, vertexShader: ` @@ -1196,10 +1218,8 @@ function createGroundFootprintMaterial() { return new THREE.ShaderMaterial({ transparent: true, side: THREE.DoubleSide, + depthTest: false, depthWrite: false, - polygonOffset: true, - polygonOffsetFactor: -2, - polygonOffsetUnits: -2, uniforms: { uColor: { value: new THREE.Color(0xffffff) }, uOpacity: { value: 0.46 }, @@ -1288,6 +1308,12 @@ function createGroundFootprintMaterial() { } 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); float sunFacing = dot(worldNormal, sunDir); 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( LOCKED_HALO_CORE_RADIUS, LOCKED_HALO_CORE_SEGMENTS, ); const dotMaterial = new THREE.MeshBasicMaterial({ - color: 0xffd25a, + color: new THREE.Color(color), transparent: true, opacity: 0.96, depthTest: false, @@ -1740,7 +1766,7 @@ function showSelfGlowStyle(position) { lockedHaloMesh = new THREE.Mesh( new THREE.CircleGeometry(LOCKED_HALO_RADIUS, 64), - createLockedHaloMaterial(), + createLockedHaloMaterial(color), ); lockedHaloMesh.renderOrder = SATELLITE_CONFIG.overlayRenderOrder + 1; sceneRef?.add(lockedHaloMesh); @@ -1750,7 +1776,7 @@ function showSelfGlowStyle(position) { function showGroundFootprintStyle(position) { if (!earthObjRef) return; lockedGroundFootprintMesh = new THREE.Group(); - lockedGroundFootprintMesh.renderOrder = SATELLITE_CONFIG.overlayRenderOrder - 1; + lockedGroundFootprintMesh.renderOrder = 0; const fill = new THREE.Mesh( new THREE.BufferGeometry(), createGroundFootprintMaterial(), @@ -1766,7 +1792,7 @@ function showIridiumReservedStyle(position) { lockedIridiumFootprintMesh = createIridiumFootprintAdapter({ earthObj: earthObjRef, earthRadiusWorld: CONFIG.earthRadius, - renderOrder: SATELLITE_CONFIG.overlayRenderOrder - 1, + renderOrder: 0, }); 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; const ringTexture = createRingTexture( 8, 12, - isLocked ? "#ffcc00" : "#ffffff", + isLocked ? color : "#ffffff", ); + const filledTexture = isLocked + ? createFilledCircleTexture(color) + : null; const spriteMaterial = new THREE.SpriteMaterial({ map: ringTexture, transparent: true, @@ -1799,6 +1828,8 @@ function createRingSprite(position, isLocked = false) { }); const sprite = new THREE.Sprite(spriteMaterial); + sprite.userData.ringTexture = ringTexture; + sprite.userData.filledTexture = filledTexture; sprite.position.copy(position); sprite.scale.set(SATELLITE_CONFIG.ringSize, SATELLITE_CONFIG.ringSize, 1); sprite.renderOrder = SATELLITE_CONFIG.overlayRenderOrder; @@ -1806,6 +1837,26 @@ function createRingSprite(position, isLocked = false) { 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") { if (!earthObjRef) return null; @@ -1830,11 +1881,12 @@ export function showHoverRing(position, isLocked = false) { if (!earthObjRef || !position) return null; if (isLocked) { + const lockedProps = getLockedSatelliteProperties() || {}; + const legendColor = getSatelliteLegendRule(lockedProps).color; hideLockedRing(); - lockedRingSprite = createRingSprite(position, true); - const presentationMode = getSatellitePresentationMode( - getLockedSatelliteProperties() || {}, - ); + lockedRingSprite = createRingSprite(position, true, legendColor); + updateLockedMarkerVisual(isLockedSatelliteHovered()); + const presentationMode = getSatellitePresentationMode(lockedProps); if ( presentationMode === SATELLITE_PRESENTATION_MODES.STARLINK_GROUND_FOOTPRINT @@ -1845,7 +1897,7 @@ export function showHoverRing(position, isLocked = false) { ) { showIridiumReservedStyle(position); } else { - showSelfGlowStyle(position); + showSelfGlowStyle(position, legendColor); } return lockedRingSprite; } @@ -1864,7 +1916,15 @@ export function hideHoverRings() { export function hideLockedRing() { if (lockedRingSprite) { + const ringTexture = lockedRingSprite.userData?.ringTexture; + const filledTexture = lockedRingSprite.userData?.filledTexture; disposeObject3D(lockedRingSprite); + if (ringTexture && ringTexture !== lockedRingSprite.material?.map) { + ringTexture.dispose(); + } + if (filledTexture && filledTexture !== lockedRingSprite.material?.map) { + filledTexture.dispose(); + } lockedRingSprite = null; } clearLockedSatelliteStyleVisuals(); @@ -1885,21 +1945,28 @@ export function updateLockedRingPosition(position) { showHoverRing(position, true); } if (lockedRingSprite) { + const isHovered = isLockedSatelliteHovered(); + updateLockedMarkerVisual(isHovered); lockedRingSprite.position.copy(position); const ringPulse = getBreathingPulse(breathingPhase); const breathScale = 1 + (ringPulse * 2 - 1) * SATELLITE_CONFIG.breathingScaleAmplitude; + const markerSize = isHovered + ? SATELLITE_CONFIG.ringSize + : SATELLITE_CONFIG.ringSize * LOCKED_RING_IDLE_SCALE; lockedRingSprite.scale.set( - SATELLITE_CONFIG.ringSize * breathScale, - SATELLITE_CONFIG.ringSize * breathScale, + markerSize * breathScale, + markerSize * breathScale, 1, ); lockedRingSprite.material.opacity = - SATELLITE_CONFIG.breathingOpacityMin + - ringPulse * - (SATELLITE_CONFIG.breathingOpacityMax - - SATELLITE_CONFIG.breathingOpacityMin); + isHovered + ? SATELLITE_CONFIG.breathingOpacityMin + + ringPulse * + (SATELLITE_CONFIG.breathingOpacityMax - + SATELLITE_CONFIG.breathingOpacityMin) + : LOCKED_RING_IDLE_OPACITY; } if (lockedDotSprite) { diff --git a/pyproject.toml b/pyproject.toml index 45740ddc..d5517177 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "planet" -version = "0.40.0" +version = "0.40.1" description = "智能星球计划 - 态势感知系统" requires-python = ">=3.14" dependencies = [ diff --git a/uv.lock b/uv.lock index 49cab99c..81215bc6 100644 --- a/uv.lock +++ b/uv.lock @@ -475,7 +475,7 @@ wheels = [ [[package]] name = "planet" -version = "0.40.0" +version = "0.40.1" source = { virtual = "." } dependencies = [ { name = "aiofiles" },