diff --git a/VERSION b/VERSION index 43c125a0..c063aeab 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.46.2 +0.46.3 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2ddd9c95..58acec67 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -8,6 +8,16 @@ This project follows the repository versioning rule: - `improvement` -> `+0.0.1`(bugfix + 小功能混合) - `bugfix` -> `+0.0.1` +## [0.46.3] — 2026-04-30 + +Released: 2026-04-30 + +### 🐛 Fixes +- 优化 Starlink footprint 显示后的地球拖拽性能,避免旋转地球时每帧重建 footprint 大网格,同时保持现有视觉效果不变。 +- 恢复点击线缆后的呼吸透明度动画,让 locked / hover 线缆重新使用既有 pulse 配置。 + +--- + ## [0.46.2] — 2026-04-30 Released: 2026-04-30 diff --git a/docs/version-history.md b/docs/version-history.md index 26d80b95..1f5c028e 100644 --- a/docs/version-history.md +++ b/docs/version-history.md @@ -16,12 +16,13 @@ ## Current Version - `main` 当前主线历史推导到:`0.16.5` -- `dev` 当前开发分支历史推导到:`0.46.2` +- `dev` 当前开发分支历史推导到:`0.46.3` ## Timeline | Version | Type | Branch | Commit | Summary | | --- | --- | --- | --- | --- | +| `0.46.3` | bugfix | `dev` | `pending` | 优化 Starlink footprint 拖拽性能,避免旋转地球时重复重建覆盖网格,并恢复线缆点击呼吸动画 | | `0.46.2` | bugfix | `dev` | `pending` | 修复 Earth 启动加载顺序、图层 localStorage 恢复、国界线底图语义、媒体面板、船只轨迹和 Iridium footprint 显示问题,并补充 AIS 聚合计划 | | `0.46.1` | bugfix | `dev` | `pending` | 修复新增 Docs 技术文档未进前端白名单导致页面不可访问的问题,补齐英文文档并固化白名单/双语/裸文件标题检查 | | `0.46.0` | feature | `dev` | `pending` | Earth 新增通用 Interactable 图标层,统一船只、算力中心、BGP 事件/观测站交互图标,并优化登陆点与 toolbar 初始渲染 | diff --git a/frontend/package.json b/frontend/package.json index 7a7ee8da..df8a7025 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "planet-frontend", - "version": "0.46.2", + "version": "0.46.3", "private": true, "packageManager": "bun@1", "dependencies": { diff --git a/frontend/public/earth/js/main.js b/frontend/public/earth/js/main.js index d03e023a..fb1814a9 100644 --- a/frontend/public/earth/js/main.js +++ b/frontend/public/earth/js/main.js @@ -2036,7 +2036,11 @@ function applyCableVisualState() { switch (state) { case CABLE_STATE.LOCKED: case CABLE_STATE.HOVERED: - cable.material.opacity = 1; + cable.material.opacity = THREE.MathUtils.lerp( + CABLE_CONFIG.lockedOpacityMin, + CABLE_CONFIG.lockedOpacityMax, + pulse, + ); cable.material.color.setRGB(0.92, 0.98, 1.0); break; case CABLE_STATE.NORMAL: diff --git a/frontend/public/earth/js/satellites.js b/frontend/public/earth/js/satellites.js index 66c63dce..51c26f83 100644 --- a/frontend/public/earth/js/satellites.js +++ b/frontend/public/earth/js/satellites.js @@ -28,6 +28,7 @@ let lockedRingSprite = null; let lockedDotSprite = null; let lockedHaloMesh = null; let lockedGroundFootprintMesh = null; +let lockedGroundFootprintFillMesh = null; let lockedIridiumFootprintMesh = null; let predictedOrbitLine = null; let relatedSatelliteSprites = []; @@ -159,6 +160,9 @@ const GROUND_FOOTPRINT_GAP_CENTER_MAX_RATIO = 0.82; const GROUND_FOOTPRINT_GAP_WIDTH_CENTER_KM = 60; const GROUND_FOOTPRINT_GAP_WIDTH_EDGE_KM = 120; const GROUND_FOOTPRINT_GAP_LENGTH_RATIO = 1.08; +const GROUND_FOOTPRINT_REBUILD_DISTANCE = 0.001; +const GROUND_FOOTPRINT_REBUILD_DISTANCE_SQ = + GROUND_FOOTPRINT_REBUILD_DISTANCE * GROUND_FOOTPRINT_REBUILD_DISTANCE; const scratchWorldSatellitePosition = new THREE.Vector3(); const scratchToCamera = new THREE.Vector3(); @@ -168,7 +172,9 @@ const scratchFootprintLateral = new THREE.Vector3(); const scratchFootprintReference = new THREE.Vector3(); const scratchFootprintVelocity = new THREE.Vector3(); const scratchFootprintTangent = new THREE.Vector3(); +const scratchLastGroundFootprintPosition = new THREE.Vector3(); const satelliteSunDirection = new THREE.Vector3(1, 0.2, 0.4).normalize(); +let hasGroundFootprintGeometry = false; export let breathingPhase = 0; @@ -1335,13 +1341,10 @@ export function setSatelliteCamera(camera) { export function setSatelliteSunDirection(direction) { if (!direction) return; satelliteSunDirection.copy(direction).normalize(); - if (lockedGroundFootprintMesh) { - const fillMesh = lockedGroundFootprintMesh.getObjectByName("footprint-fill"); - if (fillMesh?.material?.uniforms?.uSunDirectionWorld) { - fillMesh.material.uniforms.uSunDirectionWorld.value.copy( - satelliteSunDirection, - ); - } + if (lockedGroundFootprintFillMesh?.material?.uniforms?.uSunDirectionWorld) { + lockedGroundFootprintFillMesh.material.uniforms.uSunDirectionWorld.value.copy( + satelliteSunDirection, + ); } } @@ -1586,14 +1589,12 @@ function createGroundFootprintMaterial() { }, vertexShader: ` varying vec3 vWorldPosition; - varying vec3 vWorldNormal; varying vec2 vUv; void main() { vUv = uv; vec4 worldPosition = modelMatrix * vec4(position, 1.0); vWorldPosition = worldPosition.xyz; - vWorldNormal = normalize(mat3(modelMatrix) * normal); gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, @@ -1617,7 +1618,6 @@ function createGroundFootprintMaterial() { uniform vec3 uSunDirectionWorld; uniform float uDayVisibilityBoost; varying vec3 vWorldPosition; - varying vec3 vWorldNormal; varying vec2 vUv; float bowtieHalfWidth(float xEast) { @@ -1723,6 +1723,8 @@ function clearLockedSatelliteStyleVisuals() { if (lockedGroundFootprintMesh) { disposeObjectTree(lockedGroundFootprintMesh); lockedGroundFootprintMesh = null; + lockedGroundFootprintFillMesh = null; + hasGroundFootprintGeometry = false; } if (lockedIridiumFootprintMesh) { disposeIridiumFootprintAdapter(lockedIridiumFootprintMesh, earthObjRef); @@ -1933,28 +1935,6 @@ function buildGroundFootprintGeometry(position) { ); } - function toEastNorth(alongKm, crossKm) { - const offset = alongTrack - .clone() - .multiplyScalar(alongKm) - .addScaledVector(crossTrack, crossKm); - return { - xEast: offset.dot(east), - yNorth: offset.dot(north), - }; - } - - function fromEastNorth(xEast, yNorth) { - const offset = east - .clone() - .multiplyScalar(xEast) - .addScaledVector(north, yNorth); - return { - alongKm: offset.dot(alongTrack), - crossKm: offset.dot(crossTrack), - }; - } - function bowtieHalfWidth(xEast) { const t = THREE.MathUtils.clamp( Math.abs(xEast) / Math.max(exclusionLengthKm, 1), @@ -1969,6 +1949,7 @@ function buildGroundFootprintGeometry(position) { } const vertices = []; + const uvs = []; const indices = []; const indexMap = []; @@ -2000,6 +1981,10 @@ function buildGroundFootprintGeometry(position) { ); row.push(vertices.length / 3); vertices.push(point.x, point.y, point.z); + uvs.push( + THREE.MathUtils.mapLinear(alongKm, -majorKm, majorKm, 0, 1), + THREE.MathUtils.mapLinear(crossKm, -minorKm, minorKm, 0, 1), + ); } indexMap.push(row); } @@ -2021,29 +2006,8 @@ function buildGroundFootprintGeometry(position) { "position", new THREE.Float32BufferAttribute(vertices, 3), ); - const uvs = []; - for (let iy = 0; iy <= GROUND_FOOTPRINT_GRID_Y; iy += 1) { - const crossKm = THREE.MathUtils.lerp( - -minorKm, - minorKm, - iy / GROUND_FOOTPRINT_GRID_Y, - ); - for (let ix = 0; ix <= GROUND_FOOTPRINT_GRID_X; ix += 1) { - const alongKm = THREE.MathUtils.lerp( - -majorKm, - majorKm, - ix / GROUND_FOOTPRINT_GRID_X, - ); - if (!isInsideEllipse(alongKm, crossKm)) continue; - uvs.push( - THREE.MathUtils.mapLinear(alongKm, -majorKm, majorKm, 0, 1), - THREE.MathUtils.mapLinear(crossKm, -minorKm, minorKm, 0, 1), - ); - } - } fillGeometry.setAttribute("uv", new THREE.Float32BufferAttribute(uvs, 2)); fillGeometry.setIndex(indices); - fillGeometry.computeVertexNormals(); const basisToEastNorth = { eastAlongDot: alongTrack.dot(east), @@ -2064,10 +2028,18 @@ function buildGroundFootprintGeometry(position) { function updateGroundFootprintTransform(position) { if (!lockedGroundFootprintMesh || !position || !earthObjRef) return; + if ( + hasGroundFootprintGeometry && + scratchLastGroundFootprintPosition.distanceToSquared(position) <= + GROUND_FOOTPRINT_REBUILD_DISTANCE_SQ + ) { + return; + } + const geometrySet = buildGroundFootprintGeometry(position); if (!geometrySet) return; - const fillMesh = lockedGroundFootprintMesh.getObjectByName("footprint-fill"); + const fillMesh = lockedGroundFootprintFillMesh; if (fillMesh?.geometry) fillMesh.geometry.dispose(); @@ -2089,6 +2061,8 @@ function updateGroundFootprintTransform(position) { fillMesh.material.uniforms.uNorthCrossDot.value = geometrySet.basisToEastNorth.northCrossDot; } + scratchLastGroundFootprintPosition.copy(position); + hasGroundFootprintGeometry = true; } } @@ -2130,6 +2104,8 @@ function showGroundFootprintStyle(position) { fill.name = "footprint-fill"; fill.renderOrder = GROUND_FOOTPRINT_RENDER_ORDER; lockedGroundFootprintMesh.add(fill); + lockedGroundFootprintFillMesh = fill; + hasGroundFootprintGeometry = false; earthObjRef.add(lockedGroundFootprintMesh); updateGroundFootprintTransform(position); } diff --git a/pyproject.toml b/pyproject.toml index adbea373..a6b50f0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "planet" -version = "0.46.2" +version = "0.46.3" description = "智能星球计划 - 态势感知系统" requires-python = ">=3.14" dependencies = [ diff --git a/uv.lock b/uv.lock index 4c0cb57a..b3bcdcaa 100644 --- a/uv.lock +++ b/uv.lock @@ -475,7 +475,7 @@ wheels = [ [[package]] name = "planet" -version = "0.46.2" +version = "0.46.3" source = { virtual = "." } dependencies = [ { name = "aiofiles" },