release: bump version to 0.40.1
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 新闻接入通用巡航与专用卡片链路,系统日志页升级为结构化时间/级别过滤与真正字符串检索 |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "planet-frontend",
|
||||
"version": "0.40.0",
|
||||
"version": "0.40.1",
|
||||
"private": true,
|
||||
"packageManager": "bun@1",
|
||||
"dependencies": {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "planet"
|
||||
version = "0.40.0"
|
||||
version = "0.40.1"
|
||||
description = "智能星球计划 - 态势感知系统"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
|
||||
Reference in New Issue
Block a user