release: bump version to 0.76.1
This commit is contained in:
@@ -8,6 +8,20 @@ This project follows the repository versioning rule:
|
||||
- `improvement` -> `+0.0.1`(bugfix + 小功能混合)
|
||||
- `bugfix` -> `+0.0.1`
|
||||
|
||||
## [0.76.1] — 2026-10-01
|
||||
|
||||
Released: 2026-10-01
|
||||
|
||||
### Highlights
|
||||
- 修复选中 BGP 事件静态光晕在地表边缘被切断或陷入地表的问题。
|
||||
|
||||
### Added / Fixed / Improved
|
||||
- 抽取通用整体锚点遮挡能力,BGP 事件保持原有图标样式并复用圆角图标的前后显隐规则。
|
||||
- 选中事件光晕与事件图标共享地表锚点、前景层级和前后显隐路径,清理中间态代码与资源。
|
||||
- 补充中英文渲染层说明、回归测试和浏览器验证记录。
|
||||
|
||||
---
|
||||
|
||||
## [0.76.0] — 2026-09-30
|
||||
|
||||
Released: 2026-09-30
|
||||
|
||||
@@ -98,10 +98,10 @@ Base, tint, land fill and HD surface now use `createEarthSurfaceGeometry()` and
|
||||
|
||||
### Surface marker alignment and limb visibility
|
||||
|
||||
Cables and landing points share R+0.48, the `earthSurfacePoint` triangle projection and renderOrder=1. Related landing halos use an emphasis attribute in the existing batch, adding no Sprite or draw call. Rounded badges use GPU anchor ray/sphere occlusion: draw the entire billboard when its anchor is visible, otherwise clip it entirely. CPU picking uses the same test to reject rear anchors. Only `icon.badge` layers (including compute and collectors; vessels independently use heading arrows) use this path. BGP event rings use the same whole-anchor front/rear decision: front-side rings disable per-pixel depth testing and sit in the foreground adjacent to the event icon, preventing the Earth from slicing their edges; rear-side anchors hide the entire ring.
|
||||
Cables and landing points share R+0.48, the `earthSurfacePoint` triangle projection and renderOrder=1. Related landing halos use an emphasis attribute in the existing batch, adding no Sprite or draw call. Rounded badges use GPU anchor ray/sphere occlusion: draw the entire billboard when its anchor is visible, otherwise clip it entirely. CPU picking uses the same test to reject rear anchors. Only `icon.badge` layers (including compute and collectors; vessels independently use heading arrows) use this path. The selected BGP event's static overlay halo uses the event icon's R+0.48 anchor, foreground order and whole-anchor front/rear visibility; the existing animated BGP event ring remains a separate effect with its original behavior.
|
||||
|
||||
|
||||
The `.map-clusters` projection layer uses z-index 8 below HUD and whole-marker occlusion from real surface anchors. Ordinary points retain their Three.js layers; transitioning members render only once in the projection layer. Spider legs use widened transparent hit areas; hover highlights the matching line and child, and both clicks resolve the same business object. Wheel input over projected markers and legs continues to zoom the globe. Satellite selection/hover rings and self-glow use the same whole-anchor front/rear decision: front-side overlays render in full, while rear-side overlays hide together so the Earth cannot slice their edges.
|
||||
The `.map-clusters` projection layer uses z-index 8 below HUD and whole-marker occlusion from real surface anchors. Ordinary points retain their Three.js layers; transitioning members render only once in the projection layer. Spider legs use widened transparent hit areas; hover highlights the matching line and child, and both clicks resolve the same business object. Wheel input over projected markers and legs continues to zoom the globe.
|
||||
|
||||
Co-located markers share a compact projection stack across categories; screen collisions never merge different locations. A selected child uses projection `z-index: 1000`, while the locked overlay for an unclustered marker uses the shared foreground `renderOrder >= 100`, so focus stays on top across layers; clearing focus restores stable ID order. The aggregate badge stays below a selected child during its opening tween. `icon_presentation_screen` lets detail connectors follow display offsets while markers retain their real anchors. Ownership changes immediately rebuild the corresponding GPU batch.
|
||||
|
||||
|
||||
@@ -100,10 +100,10 @@ Earth 的地表不是单一 mesh,而是多层近似同心球:基座球、海
|
||||
|
||||
### 地表标记对齐与侧面显隐
|
||||
|
||||
海缆和登陆点现在统一使用 R+0.48 及 `earthSurfacePoint` 三角表面投影,并保持 renderOrder=1。登陆点关联光晕进入原实例批次的 emphasis 属性,不增加独立 Sprite 或 draw call。侧面的完整圆角徽标采用 GPU 锚点射线/球体遮挡:锚点可见则整个 billboard 绘制,锚点被地球挡住则整块裁除。CPU 拾取使用同样的射线判断,避免点到背面图标。该路径只作用于 `icon.badge` 层(含算力、观测站;船舶独立使用航向箭头)。BGP 事件圆环同样按锚点做整块前后判断,前面锚点的圆环关闭逐像素深度测试并置于事件图标邻近前景层,避免圆环边缘被地球切断;背面锚点整圈隐藏。
|
||||
海缆和登陆点现在统一使用 R+0.48 及 `earthSurfacePoint` 三角表面投影,并保持 renderOrder=1。登陆点关联光晕进入原实例批次的 emphasis 属性,不增加独立 Sprite 或 draw call。侧面的完整圆角徽标采用 GPU 锚点射线/球体遮挡:锚点可见则整个 billboard 绘制,锚点被地球挡住则整块裁除。CPU 拾取使用同样的射线判断,避免点到背面图标。该路径只作用于 `icon.badge` 层(含算力、观测站;船舶独立使用航向箭头)。选中 BGP 事件的静态覆盖光晕使用事件图标相同的 R+0.48 锚点、前景顺序和整块前后显隐;BGP 发散圆环仍保留原有独立动画语义。
|
||||
|
||||
|
||||
聚合投影层 `.map-clusters` 的 z-index 为 8,低于 HUD;以真实地表锚点做整标遮挡。普通点仍使用原 Three.js 图层,过渡中的成员只在投影层显示一次,避免双图标。Spiderfy 引线有透明加宽命中区域,悬停联动高亮引线与子点,点击返回同一业务对象;滚轮在图标与引线上继续缩放地球。卫星选中/悬停环和自发光也按卫星锚点做整块前后判断,前侧完整显示,背侧整组隐藏,避免环或光晕被地表切边。
|
||||
聚合投影层 `.map-clusters` 的 z-index 为 8,低于 HUD;以真实地表锚点做整标遮挡。普通点仍使用原 Three.js 图层,过渡中的成员只在投影层显示一次,避免双图标。Spiderfy 引线有透明加宽命中区域,悬停联动高亮引线与子点,点击返回同一业务对象;滚轮在图标与引线上继续缩放地球。
|
||||
|
||||
跨图层同坐标/极近坐标标记在同一投影层内紧凑堆叠,不以屏幕碰撞合并不同地点;选中项使用投影子点 `z-index: 1000`,普通未聚合图标的锁定覆盖层使用共享的前景 `renderOrder >= 100`,因此跨图层重叠时仍由焦点对象在最上层;取消后恢复稳定 ID 顺序。聚合徽标在展开过渡期间保持低于选中子点。`icon_presentation_screen` 仅供详情引线跟随显示偏移,真实锚点仍由原 marker 持有;隐藏状态变化立即重建对应 GPU 批次。
|
||||
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
## Current Version
|
||||
|
||||
- `main` 当前主线历史推导到:`0.16.5`
|
||||
- `dev` 当前开发分支历史推导到:`0.76.0`
|
||||
- `dev` 当前开发分支历史推导到:`0.76.1`
|
||||
|
||||
## Timeline
|
||||
|
||||
| Version | Type | Branch | Commit | Summary |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `0.76.1` | bugfix | `dev` | `v0.76.1` | 修复选中 BGP 事件静态光晕地表遮挡,统一事件锚点显隐与前景偏移,清理中间态逻辑并补齐验证 |
|
||||
| `0.76.0` | feature | `dev` | `v0.76.0` | 详情卡默认配图与后台配置、五类算力中心、统一地表拾取与选中置顶、卡片交互修复及 Earth 性能优化 |
|
||||
| `0.75.4` | improvement | `dev` | `v0.75.4` | 图例统一筛选、卫星 Points 点径与性能优化、FPS、菜单 DPI/hover、星空 sRGB、地表对齐及新闻编码修复,清理废弃代码文档 |
|
||||
| `0.75.3` | improvement | `dev` | `v0.75.3` | 地球海陆材质与可读夜侧、连续拖拽、现代徽标、卫星平滑开放轨道及流光,修复 CelesTrak 缓存与任务历史并清理废弃实现 |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "planet-frontend",
|
||||
"version": "0.76.0",
|
||||
"version": "0.76.1",
|
||||
"private": true,
|
||||
"packageManager": "bun@1",
|
||||
"dependencies": {
|
||||
|
||||
@@ -23,7 +23,6 @@ let totalAnomalyCount = 0;
|
||||
let totalIncidentCount = 0;
|
||||
let textureCache = null;
|
||||
let eventRingTextureCache = null;
|
||||
let activeEventOverlay = null;
|
||||
let activeCollectorOverlayContext = null;
|
||||
const relativeTimeFormatter = new Intl.RelativeTimeFormat("zh-CN", {
|
||||
numeric: "auto",
|
||||
@@ -33,7 +32,6 @@ const colorScratchB = new THREE.Color();
|
||||
const COLLECTOR_SCAN_SPEED_RAD = 0.00018;
|
||||
const COLLECTOR_SCAN_REBUILD_MS = 80;
|
||||
const BGP_EVENT_RENDER_ORDER = 4.5;
|
||||
const BGP_EVENT_RING_RENDER_ORDER = BGP_EVENT_RENDER_ORDER + 0.02;
|
||||
const BGP_EVENT_POINT_SIZE = 34;
|
||||
const BGP_EVENT_SYMBOL_SIZE = 60;
|
||||
const BGP_COLLECTOR_RENDER_ORDER = 4.4;
|
||||
@@ -909,6 +907,16 @@ function createOverlaySprite({ color, opacity, scale }) {
|
||||
return sprite;
|
||||
}
|
||||
|
||||
function updateEventHaloVisibility(camera) {
|
||||
const earth = bgpEventOverlayGroup.parent?.parent;
|
||||
if (!earth || !camera || bgpEventOverlayGroup.children.length === 0) return;
|
||||
bgpCameraLocalScratch.copy(camera.position);
|
||||
earth.worldToLocal(bgpCameraLocalScratch);
|
||||
bgpEventOverlayGroup.children.forEach((halo) => {
|
||||
halo.visible = isSurfaceAnchorVisible(halo.position, bgpCameraLocalScratch, CONFIG.earthRadius);
|
||||
});
|
||||
}
|
||||
|
||||
function projectLatLon(lat, lon, bearingDeg, distanceDeg) {
|
||||
const latRad = (lat * Math.PI) / 180;
|
||||
const lonRad = (lon * Math.PI) / 180;
|
||||
@@ -1123,16 +1131,12 @@ function createEventRingSprite(marker) {
|
||||
transparent: true,
|
||||
opacity: 0,
|
||||
depthWrite: false,
|
||||
// The ring is a billboard around a surface anchor. Testing every pixel
|
||||
// against Earth slices its sides at the limb even when the anchor is
|
||||
// visible; visibility is decided once for the whole billboard below.
|
||||
depthTest: false,
|
||||
depthTest: true,
|
||||
blending: THREE.AdditiveBlending,
|
||||
}),
|
||||
);
|
||||
ring.position.copy(marker.position);
|
||||
ring.renderOrder = BGP_EVENT_RING_RENDER_ORDER;
|
||||
marker.userData.bgp_event_anchor_visible = true;
|
||||
ring.renderOrder = BGP_EVENT_RENDER_ORDER - 0.05;
|
||||
ring.visible = showBGP && !marker.userData.legend_filtered && !marker.userData.icon_presentation_hidden;
|
||||
return ring;
|
||||
}
|
||||
@@ -1369,11 +1373,7 @@ export async function loadBGPAnomalies(scene, earth) {
|
||||
|
||||
export function updateBGPVisualState(lockedObjectType, lockedObject, camera, cruiseMarker = null) {
|
||||
const now = performance.now();
|
||||
const earth = bgpGroup.parent;
|
||||
if (earth && camera?.position) {
|
||||
bgpCameraLocalScratch.copy(camera.position);
|
||||
earth.worldToLocal(bgpCameraLocalScratch);
|
||||
}
|
||||
updateEventHaloVisibility(camera);
|
||||
updateCollectorOverlayScan(lockedObjectType, lockedObject);
|
||||
syncBGPLegendEffects();
|
||||
const hasLockedLayer = Boolean(
|
||||
@@ -1540,10 +1540,6 @@ export function updateBGPVisualState(lockedObjectType, lockedObject, camera, cru
|
||||
}
|
||||
|
||||
const ringPhaseA = (now * BGP_CONFIG.ring.speed + marker.userData.pulseOffset) % 1;
|
||||
const ringAnchorVisible = !earth || !camera?.position
|
||||
? true
|
||||
: isSurfaceAnchorVisible(marker.position, bgpCameraLocalScratch, CONFIG.earthRadius);
|
||||
marker.userData.bgp_event_anchor_visible = ringAnchorVisible;
|
||||
const applyRingState = (ring, phase, maxScale) => {
|
||||
if (!ring) return;
|
||||
const progress = Math.max(0, Math.min(1, phase));
|
||||
@@ -1557,8 +1553,8 @@ export function updateBGPVisualState(lockedObjectType, lockedObject, camera, cru
|
||||
ring.scale.setScalar(desiredWorldScale);
|
||||
ring.material.color.setHex(markerColor);
|
||||
ring.material.opacity = showBGP ? ringBaseOpacity * visibility : 0;
|
||||
ring.visible = showBGP && ringAnchorVisible && !marker.userData.legend_filtered && !marker.userData.icon_presentation_hidden;
|
||||
ring.renderOrder = isActive ? BGP_EVENT_RENDER_ORDER + 0.15 : BGP_EVENT_RING_RENDER_ORDER;
|
||||
ring.visible = showBGP && !marker.userData.legend_filtered && !marker.userData.icon_presentation_hidden;
|
||||
ring.renderOrder = isActive ? BGP_EVENT_RENDER_ORDER + 0.15 : BGP_EVENT_RENDER_ORDER - 0.05;
|
||||
};
|
||||
|
||||
applyRingState(marker.userData.ringA, ringPhaseA, BGP_CONFIG.ring.scaleA);
|
||||
@@ -1703,8 +1699,6 @@ export function showBGPEventOverlay(marker, earth) {
|
||||
typeof region?.longitude === "number",
|
||||
);
|
||||
if (validRegions.length === 0) return;
|
||||
const overlayItems = [];
|
||||
|
||||
validRegions.forEach((region) => {
|
||||
const eventBaseColor = marker.userData.baseColor || getSeverityColor(marker.userData.severity);
|
||||
const halo = createOverlaySprite({
|
||||
@@ -1712,19 +1706,19 @@ export function showBGPEventOverlay(marker, earth) {
|
||||
opacity: 0.24,
|
||||
scale: BGP_CONFIG.regionScale,
|
||||
});
|
||||
halo.position.copy(
|
||||
latLonToVector3(
|
||||
region.latitude,
|
||||
region.longitude,
|
||||
CONFIG.earthRadius + BGP_COLLECTOR_ALTITUDE_OFFSET - 0.1,
|
||||
),
|
||||
const anchor = latLonToVector3(
|
||||
region.latitude,
|
||||
region.longitude,
|
||||
CONFIG.earthRadius + BGP_CONFIG.altitudeOffset,
|
||||
);
|
||||
halo.renderOrder = 2;
|
||||
halo.position.copy(anchor);
|
||||
// Like marker badges, a visible anchor owns the entire billboard. Per-pixel
|
||||
// Earth depth would cut this camera-facing gradient through the surface.
|
||||
halo.material.depthTest = false;
|
||||
halo.renderOrder = BGP_EVENT_RENDER_ORDER;
|
||||
bgpEventOverlayGroup.add(halo);
|
||||
overlayItems.push(halo);
|
||||
});
|
||||
|
||||
activeEventOverlay = overlayItems;
|
||||
activeCollectorOverlayContext = null;
|
||||
bgpOverlayGroup.visible = showBGP;
|
||||
}
|
||||
@@ -1784,7 +1778,6 @@ export function showBGPCollectorCoverageOverlay(marker, earth, options = {}) {
|
||||
innerRing.renderOrder = 3;
|
||||
bgpCollectorRadarGroup.add(innerRing);
|
||||
|
||||
const overlayItems = [halo, pulseHalo, innerRing];
|
||||
const anchorLatitude = marker.userData.displayLatitude ?? marker.userData.latitude;
|
||||
const anchorLongitude = marker.userData.displayLongitude ?? marker.userData.longitude;
|
||||
const activityMagnitude = Math.log2(prefixCount + observationCount + 1);
|
||||
@@ -1840,7 +1833,6 @@ export function showBGPCollectorCoverageOverlay(marker, earth, options = {}) {
|
||||
);
|
||||
sectorFill.renderOrder = 2;
|
||||
bgpCollectorRadarGroup.add(sectorFill);
|
||||
overlayItems.push(sectorFill);
|
||||
|
||||
const outerArc = createCoverageBoundaryLine(
|
||||
outerArcPoints,
|
||||
@@ -1849,7 +1841,6 @@ export function showBGPCollectorCoverageOverlay(marker, earth, options = {}) {
|
||||
);
|
||||
outerArc.renderOrder = 3;
|
||||
bgpCollectorRadarGroup.add(outerArc);
|
||||
overlayItems.push(outerArc);
|
||||
|
||||
const leftBoundary = createCoverageBoundaryLine(
|
||||
leftBoundaryPoints,
|
||||
@@ -1858,7 +1849,6 @@ export function showBGPCollectorCoverageOverlay(marker, earth, options = {}) {
|
||||
);
|
||||
leftBoundary.renderOrder = 3;
|
||||
bgpCollectorRadarGroup.add(leftBoundary);
|
||||
overlayItems.push(leftBoundary);
|
||||
|
||||
const rightBoundary = createCoverageBoundaryLine(
|
||||
rightBoundaryPoints,
|
||||
@@ -1867,9 +1857,7 @@ export function showBGPCollectorCoverageOverlay(marker, earth, options = {}) {
|
||||
);
|
||||
rightBoundary.renderOrder = 3;
|
||||
bgpCollectorRadarGroup.add(rightBoundary);
|
||||
overlayItems.push(rightBoundary);
|
||||
|
||||
activeEventOverlay = overlayItems;
|
||||
activeCollectorOverlayContext = {
|
||||
marker,
|
||||
earth,
|
||||
@@ -1880,7 +1868,6 @@ export function showBGPCollectorCoverageOverlay(marker, earth, options = {}) {
|
||||
}
|
||||
|
||||
export function clearBGPEventOverlay() {
|
||||
activeEventOverlay = null;
|
||||
activeCollectorOverlayContext = null;
|
||||
clearGroup(bgpEventOverlayGroup);
|
||||
clearGroup(bgpCollectorRadarGroup);
|
||||
@@ -1939,6 +1926,6 @@ export function applyBGPLegendFilter() {
|
||||
bgpCollectorIconLayer.refreshFilter();
|
||||
syncBGPLegendEffects();
|
||||
anomalyMarkers.forEach(marker => {
|
||||
if (marker.userData.ringA) marker.userData.ringA.visible = showBGP && marker.userData.bgp_event_anchor_visible !== false && !marker.userData.legend_filtered && !marker.userData.icon_presentation_hidden;
|
||||
if (marker.userData.ringA) marker.userData.ringA.visible = showBGP && !marker.userData.legend_filtered && !marker.userData.icon_presentation_hidden;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1279,21 +1279,10 @@ export function isSatelliteFrontFacing(index, camera = cameraRef) {
|
||||
);
|
||||
}
|
||||
|
||||
function updateSatelliteOverlayVisibility() {
|
||||
const lockedVisible =
|
||||
lockedSatelliteIndex === null || isSatelliteFrontFacing(lockedSatelliteIndex);
|
||||
const hoveredVisible =
|
||||
hoveredSatelliteIndex === null || isSatelliteFrontFacing(hoveredSatelliteIndex);
|
||||
if (lockedRingSprite) lockedRingSprite.visible = lockedVisible;
|
||||
if (lockedDotSprite) lockedDotSprite.visible = lockedVisible;
|
||||
if (lockedHaloMesh) lockedHaloMesh.visible = lockedVisible;
|
||||
if (hoverRingSprite) hoverRingSprite.visible = hoveredVisible;
|
||||
}
|
||||
|
||||
function createLockedHaloMaterial(color = "#ffbf47") {
|
||||
return new THREE.ShaderMaterial({
|
||||
transparent: true,
|
||||
depthTest: false,
|
||||
depthTest: true,
|
||||
depthWrite: false,
|
||||
side: THREE.DoubleSide,
|
||||
uniforms: {
|
||||
@@ -1735,7 +1724,7 @@ function showSelfGlowStyle(position, color = "#ffd25a") {
|
||||
color: new THREE.Color(color),
|
||||
transparent: true,
|
||||
opacity: 0.96,
|
||||
depthTest: false,
|
||||
depthTest: true,
|
||||
depthWrite: false,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
@@ -1808,7 +1797,7 @@ function createRingSprite(position, isLocked = false, color = "#ffffff") {
|
||||
map: ringTexture,
|
||||
transparent: true,
|
||||
opacity: 0.8,
|
||||
depthTest: false,
|
||||
depthTest: true,
|
||||
depthWrite: false,
|
||||
alphaTest: 0.01,
|
||||
sizeAttenuation: false,
|
||||
@@ -1856,7 +1845,6 @@ export function showHoverRing(position, isLocked = false) {
|
||||
const legendColor = getSatelliteLegendRule(lockedProps).color;
|
||||
hideLockedRing();
|
||||
lockedRingSprite = createRingSprite(position, true, legendColor);
|
||||
updateSatelliteOverlayVisibility();
|
||||
updateLockedMarkerVisual(isLockedSatelliteHovered());
|
||||
const presentationMode = getSatellitePresentationMode(lockedProps);
|
||||
if (
|
||||
@@ -1877,7 +1865,6 @@ export function showHoverRing(position, isLocked = false) {
|
||||
hideHoverRings();
|
||||
const hoverColor = getSatelliteLegendRule(satelliteData[hoveredSatelliteIndex]?.properties).color;
|
||||
hoverRingSprite = createRingSprite(position, false, hoverColor);
|
||||
updateSatelliteOverlayVisibility();
|
||||
return hoverRingSprite;
|
||||
}
|
||||
|
||||
@@ -1972,7 +1959,6 @@ export function updateLockedRingPosition(position) {
|
||||
if (lockedIridiumFootprintMesh) {
|
||||
updateIridiumReservedStyle(position);
|
||||
}
|
||||
updateSatelliteOverlayVisibility();
|
||||
}
|
||||
|
||||
export function updateHoverRingPosition(position) {
|
||||
@@ -1983,7 +1969,6 @@ export function updateHoverRingPosition(position) {
|
||||
SATELLITE_CONFIG.ringSize,
|
||||
1,
|
||||
);
|
||||
updateSatelliteOverlayVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "planet"
|
||||
version = "0.76.0"
|
||||
version = "0.76.1"
|
||||
description = "智能星球计划 - 态势感知系统"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
|
||||
Reference in New Issue
Block a user