diff --git a/TODO.md b/TODO.md index 03564098..2ddaf1e6 100644 --- a/TODO.md +++ b/TODO.md @@ -15,3 +15,4 @@ - [ ] 把 RIR delegated / `inetnum` / `inet6num` whois 设计成 prefix geography 的 fallback,而不是主来源 - [x] 在 activity layer 之后继续补 `route leak` 和 `path instability / flap` detector - [ ] 对 [frontend/public/earth/js/bgp.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/bgp.js) 做按职责拆分的小重构,拆成 data / markers / overlays / animation,降低后续维护复杂度 +- [ ] 可选优化(非必做):将 BGP incident/collector 标点改为 HTML marker(参考 worldmonitor 的 `htmlElementsData` 思路),实现近乎固定屏幕尺寸与更高密度可点击性 diff --git a/VERSION b/VERSION index 5b0a59e3..69ab04c4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.22.11 +0.22.12 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 0674b086..cca1b5b8 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,32 @@ This project follows the repository versioning rule: - `feature` -> `+0.1.0` - `bugfix` -> `+0.0.1` +## 0.22.12 + +Released: 2026-04-02 + +### Highlights + +- Completed Earth `BGP / cables / landing points` visual-parameter consolidation so the latest overlap/size fixes are no longer scattered as magic numbers. +- Shipped a bugfix version bump for the constant-extraction and marker-size stabilization work (`+0.0.1`). + +### Improved + +- Improved [constants.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/constants.js) by introducing structured `BGP_CONFIG` groups (`marker`, `pulse`, `ring`, `halo`, `sizeStabilization`) and by extracting additional cable/landing-point render parameters into `CABLE_CONFIG`. +- Improved [bgp.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/bgp.js) by fully switching BGP marker/ring/pulse logic to the new nested constants, including collector status-core scale floors and distance/FOV-based size-stabilization knobs. +- Improved [cables.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/cables.js) by removing hardcoded landing-point visual constants and centralizing base scale, pulse, dimming, opacity, emissive, and stabilization thresholds. +- Improved [main.js](/home/ray/dev/linkong/planet/frontend/public/earth/js/main.js) by passing `camera` through landing-point visual update/reset flows so stabilized sizing remains consistent during normal render-loop interactions. +- Improved planning continuity in [TODO.md](/home/ray/dev/linkong/planet/TODO.md) by documenting the optional `HTML marker` migration path for near-fixed screen-size BGP markers as a non-blocking follow-up. + +### Fixed + +- Fixed drift from earlier iterative tuning where BGP/cable size and animation behavior depended on multiple dispersed literals, which made overlap and readability regressions more likely during subsequent tweaks. +- Fixed a partially applied refactor state where some BGP collector/status-core scaling still bypassed shared config, preventing clean global tuning. + +### Notes + +- During this round, an over-tight marker-size clamp path was intentionally not kept; the final shipped config uses wider stabilization bounds so zoom-level response remains visible while still reducing overlap blow-up. + ## 0.22.11 Released: 2026-04-02 diff --git a/frontend/package.json b/frontend/package.json index 0edc3c88..dfaceb60 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "planet-frontend", - "version": "0.22.11", + "version": "0.22.12", "private": true, "dependencies": { "@ant-design/icons": "^5.2.6", diff --git a/frontend/public/earth/js/bgp.js b/frontend/public/earth/js/bgp.js index 071987a3..08927270 100644 --- a/frontend/public/earth/js/bgp.js +++ b/frontend/public/earth/js/bgp.js @@ -277,13 +277,39 @@ function blendHexColors(fromHex, toHex, ratio) { } function getCollectorDistanceScale(marker, camera) { - if (!marker || !camera) return 1; + if (!marker || !camera || BGP_CONFIG.sizeStabilization?.enabled === false) return 1; marker.getWorldPosition(collectorWorldPosition); const distanceToCamera = camera.position.distanceTo(collectorWorldPosition); const referenceDistance = CONFIG.defaultCameraZ - CONFIG.earthRadius + BGP_CONFIG.collectorAltitudeOffset; + const referenceFovRad = (75 * Math.PI) / 180; + const cameraFovRad = ((camera.fov || 75) * Math.PI) / 180; + const min = Number(BGP_CONFIG.sizeStabilization?.collectorMin ?? 0.6); + const max = Number(BGP_CONFIG.sizeStabilization?.collectorMax ?? 1.9); + const worldPerPixel = + distanceToCamera * Math.tan(cameraFovRad / 2); + const referenceWorldPerPixel = + referenceDistance * Math.tan(referenceFovRad / 2); - return clamp(distanceToCamera / referenceDistance, 0.6, 1.9); + return clamp(worldPerPixel / referenceWorldPerPixel, min, max); +} + +function getEventDistanceScale(marker, camera) { + if (!marker || !camera || BGP_CONFIG.sizeStabilization?.enabled === false) return 1; + + marker.getWorldPosition(collectorWorldPosition); + const distanceToCamera = camera.position.distanceTo(collectorWorldPosition); + const referenceDistance = CONFIG.defaultCameraZ - CONFIG.earthRadius + BGP_CONFIG.altitudeOffset; + const referenceFovRad = (75 * Math.PI) / 180; + const cameraFovRad = ((camera.fov || 75) * Math.PI) / 180; + const min = Number(BGP_CONFIG.sizeStabilization?.eventMin ?? 0.7); + const max = Number(BGP_CONFIG.sizeStabilization?.eventMax ?? 1.9); + const worldPerPixel = + distanceToCamera * Math.tan(cameraFovRad / 2); + const referenceWorldPerPixel = + referenceDistance * Math.tan(referenceFovRad / 2); + + return clamp(worldPerPixel / referenceWorldPerPixel, min, max); } function orientCollectorMarkerToSurface(marker, position) { @@ -332,13 +358,13 @@ function getCollectorActivityProfile(markerData) { const scaleBoost = clamp(1 + Math.log2(activityScore + 1) * 0.12, 1, 1.55); const haloScale = - BGP_CONFIG.collectorHaloScale + + BGP_CONFIG.halo.collectorScale + clamp(Math.log2(recent24h + prefixes + 1) * 2.2, 0, 12); const pulseHaloScale = - BGP_CONFIG.collectorPulseHaloScale + + BGP_CONFIG.halo.collectorPulseScale + clamp(Math.log2(recent24h + recent7d + 1) * 2.8, 0, 14); const coverageHaloScale = - BGP_CONFIG.collectorCoverageHaloScale + + BGP_CONFIG.halo.collectorCoverageScale + clamp(Math.log2(prefixes + origins + 1) * 3.4, 0, 18); return { @@ -994,7 +1020,7 @@ function createCollectorMarker(markerData) { ); marker.position.copy(position); - marker.scale.set(BGP_CONFIG.collectorScale * 0.88 * activity.scaleBoost, BGP_CONFIG.collectorScale * 1.08 * activity.scaleBoost, 1); + marker.scale.set(BGP_CONFIG.marker.collectorBaseScale * 0.88 * activity.scaleBoost, BGP_CONFIG.marker.collectorBaseScale * 1.08 * activity.scaleBoost, 1); marker.renderOrder = 3; marker.visible = showBGP; orientCollectorMarkerToSurface(marker, position); @@ -1018,7 +1044,11 @@ function createCollectorMarker(markerData) { const statusCore = createOverlaySprite({ color: activity.color, opacity: 0.0, - scale: Math.max(BGP_CONFIG.collectorScale * 0.12, 1.2), + scale: Math.max( + BGP_CONFIG.marker.collectorBaseScale * + BGP_CONFIG.marker.collectorStatusCoreBaseScale, + BGP_CONFIG.marker.collectorStatusCoreMinScale, + ), }); statusCore.position.set(0, 0, 0.02); statusCore.renderOrder = 4; @@ -1036,7 +1066,7 @@ function createCollectorMarker(markerData) { marker.userData = { type: "bgp_collector", state: "normal", - baseScale: BGP_CONFIG.collectorScale * activity.scaleBoost, + baseScale: BGP_CONFIG.marker.collectorBaseScale * activity.scaleBoost, baseColor, idleColor, pulseOffset: Math.random() * Math.PI * 2, @@ -1072,7 +1102,7 @@ function createAnomalyMarker(markerData) { CONFIG.earthRadius + BGP_CONFIG.altitudeOffset, ); - const baseScale = BGP_CONFIG.baseScale * getSeverityScale(markerData.severity); + const baseScale = BGP_CONFIG.marker.eventBaseScale * getSeverityScale(markerData.severity); sprite.position.copy(position); sprite.scale.setScalar(baseScale); sprite.renderOrder = 5; @@ -1097,7 +1127,7 @@ function createAnomalyMarker(markerData) { blending: THREE.AdditiveBlending, }), ); - ringA.scale.setScalar(baseScale * BGP_CONFIG.eventRingScaleA); + ringA.scale.setScalar(baseScale * BGP_CONFIG.ring.scaleA); ringA.position.set(0, 0, -0.01); sprite.add(ringA); @@ -1112,7 +1142,7 @@ function createAnomalyMarker(markerData) { blending: THREE.AdditiveBlending, }), ); - ringB.scale.setScalar(baseScale * BGP_CONFIG.eventRingScaleB); + ringB.scale.setScalar(baseScale * BGP_CONFIG.ring.scaleB); ringB.position.set(0, 0, -0.02); sprite.add(ringB); @@ -1338,7 +1368,7 @@ export function updateBGPVisualState(lockedObjectType, lockedObject, camera) { 0.5 + 0.5 * Math.sin( - now * BGP_CONFIG.collectorPulseSpeed + marker.userData.pulseOffset, + now * BGP_CONFIG.pulse.collectorSpeed + marker.userData.pulseOffset, ); let scale = marker.userData.baseScale * getCollectorDistanceScale(marker, camera); @@ -1411,7 +1441,11 @@ export function updateBGPVisualState(lockedObjectType, lockedObject, camera) { isLocked ? 0.58 : isHovered ? 0.4 : hasLockedLayer ? 0.0 : 0.18; marker.userData.statusCore.material.color.setHex(marker.userData.baseColor || BGP_CONFIG.collectorColor); marker.userData.statusCore.scale.setScalar( - Math.max(BGP_CONFIG.collectorScale * 0.12, 1.2) * (isLocked ? 1.08 : isHovered ? 1.04 : 0.92), + Math.max( + BGP_CONFIG.marker.collectorBaseScale * + BGP_CONFIG.marker.collectorStatusCoreBaseScale, + BGP_CONFIG.marker.collectorStatusCoreMinScale, + ) * (isLocked ? 1.08 : isHovered ? 1.04 : 0.92), ); } if (marker.userData.coverageHalo) { @@ -1433,33 +1467,35 @@ export function updateBGPVisualState(lockedObjectType, lockedObject, camera) { const isHovered = marker.userData.state === "hover"; const pulse = 0.5 + - 0.5 * Math.sin(now * BGP_CONFIG.pulseSpeed + marker.userData.pulseOffset); + 0.5 * Math.sin(now * BGP_CONFIG.pulse.eventSpeed + marker.userData.pulseOffset); - let scale = marker.userData.baseScale; + const iconAnchorScale = + marker.userData.baseScale * getEventDistanceScale(marker, camera); + let scale = iconAnchorScale; let opacity = BGP_CONFIG.opacity.normal; let markerColor = marker.userData.baseColor || getSeverityColor(marker.userData.severity); const isIncidentMarker = marker.userData.source === "bgp_incident"; let ringBaseOpacity = isIncidentMarker - ? BGP_CONFIG.eventRingOpacity - : BGP_CONFIG.eventRingOpacity * 0.45; + ? BGP_CONFIG.ring.opacity + : BGP_CONFIG.ring.opacity * 0.45; if (isLocked || isLinkedCollectorLocked) { - scale *= 1 + BGP_CONFIG.lockedPulseAmplitude * pulse; + scale *= 1 + BGP_CONFIG.pulse.lockedAmplitude * pulse; opacity = BGP_CONFIG.opacity.lockedMin + (BGP_CONFIG.opacity.lockedMax - BGP_CONFIG.opacity.lockedMin) * pulse; ringBaseOpacity *= 1.2; } else if (isHovered) { - scale *= BGP_CONFIG.hoverScale; + scale *= BGP_CONFIG.marker.hoverScale; opacity = BGP_CONFIG.opacity.hover; ringBaseOpacity *= 1.05; } else if (isOtherLocked) { - scale *= BGP_CONFIG.dimmedScale; + scale *= BGP_CONFIG.marker.dimmedScale; opacity = 0.1; markerColor = 0x7d8ca3; ringBaseOpacity = 0.02; } else { - scale *= 1 + BGP_CONFIG.normalPulseAmplitude * pulse; + scale *= 1 + BGP_CONFIG.pulse.normalAmplitude * pulse; opacity = isIncidentMarker ? 0.7 : 0.62; } @@ -1468,13 +1504,13 @@ export function updateBGPVisualState(lockedObjectType, lockedObject, camera) { marker.material.opacity = opacity; marker.visible = showBGP; - const ringPhaseA = (now * BGP_CONFIG.eventRingSpeed + marker.userData.pulseOffset) % 1; + const ringPhaseA = (now * BGP_CONFIG.ring.speed + marker.userData.pulseOffset) % 1; const applyRingState = (ring, phase, maxScale) => { if (!ring) return; const progress = Math.max(0, Math.min(1, phase)); const minScale = 1.28; const desiredWorldScale = - marker.userData.baseScale * (minScale + progress * (maxScale - minScale)); + iconAnchorScale * (minScale + progress * (maxScale - minScale)); const parentScale = Math.max(scale, 0.0001); const localRingScale = desiredWorldScale / parentScale; const fadeIn = Math.max(0, Math.min(1, (progress - 0.08) / 0.14)); @@ -1486,7 +1522,7 @@ export function updateBGPVisualState(lockedObjectType, lockedObject, camera) { ring.visible = showBGP; }; - applyRingState(marker.userData.ringA, ringPhaseA, BGP_CONFIG.eventRingScaleA); + applyRingState(marker.userData.ringA, ringPhaseA, BGP_CONFIG.ring.scaleA); if (marker.userData.ringB) { marker.userData.ringB.material.opacity = 0; marker.userData.ringB.visible = false; diff --git a/frontend/public/earth/js/cables.js b/frontend/public/earth/js/cables.js index 03048f2f..b03cb448 100644 --- a/frontend/public/earth/js/cables.js +++ b/frontend/public/earth/js/cables.js @@ -2,7 +2,13 @@ import * as THREE from "three"; -import { CONFIG, CABLE_COLORS, PATHS, CABLE_STATE } from "./constants.js"; +import { + CONFIG, + CABLE_COLORS, + PATHS, + CABLE_STATE, + CABLE_CONFIG, +} from "./constants.js"; import { latLonToVector3 } from "./utils.js"; import { updateEarthStats, showStatusMessage } from "./ui.js"; import { showInfoCard } from "./info-card.js"; @@ -14,6 +20,37 @@ export let lockedCable = null; let cableIdMap = new Map(); let cableStates = new Map(); let cablesVisible = true; +const landingPointWorldPosition = new THREE.Vector3(); + +function clamp(value, min, max) { + return Math.min(max, Math.max(min, value)); +} + +function getLandingPointDistanceScale(point, camera) { + if ( + !point || + !camera || + CABLE_CONFIG.landingPointSizeStabilization?.enabled === false + ) return 1; + point.getWorldPosition(landingPointWorldPosition); + const distanceToCamera = camera.position.distanceTo(landingPointWorldPosition); + const referenceDistance = + CONFIG.defaultCameraZ - + CONFIG.earthRadius + + CABLE_CONFIG.landingPoint.altitudeOffset; + const referenceFovDeg = + CABLE_CONFIG.landingPointSizeStabilization?.referenceFov || 75; + const referenceFovRad = (referenceFovDeg * Math.PI) / 180; + const cameraFovRad = + (((camera.fov || referenceFovDeg)) * Math.PI) / 180; + const worldPerPixel = distanceToCamera * Math.tan(cameraFovRad / 2); + const referenceWorldPerPixel = referenceDistance * Math.tan(referenceFovRad / 2); + return clamp( + worldPerPixel / referenceWorldPerPixel, + CABLE_CONFIG.landingPointSizeStabilization?.min ?? 0.12, + CABLE_CONFIG.landingPointSizeStabilization?.max ?? 3.0, + ); +} function disposeMaterial(material) { if (!material) return; @@ -83,9 +120,9 @@ function createCableLine(points, color, properties) { const lineMaterial = new THREE.LineBasicMaterial({ color, - linewidth: 1, + linewidth: CABLE_CONFIG.line.lineWidth, transparent: true, - opacity: 1.0, + opacity: CABLE_CONFIG.line.opacity, depthTest: true, depthWrite: true, }); @@ -115,7 +152,7 @@ function createCableLine(points, color, properties) { localCenter: lineGeometry.boundingSphere?.center?.clone() || new THREE.Vector3(), }; - cableLine.renderOrder = 1; + cableLine.renderOrder = CABLE_CONFIG.line.renderOrder; if (!cableIdMap.has(cableId)) { cableIdMap.set(cableId, []); @@ -149,7 +186,7 @@ function calculateGreatCirclePoints( let delta = Math.acos(Math.max(-1, Math.min(1, cosDelta))); - if (delta < 0.01) { + if (delta < CABLE_CONFIG.line.nearPointThreshold) { const p1 = latLonToVector3(lat1, lon1, radius); const p2 = latLonToVector3(lat2, lon2, radius); return [p1, p2]; @@ -248,8 +285,8 @@ export async function loadGeoJSONFromPath(scene, earthObj) { lon1, lat2, lon2, - CONFIG.earthRadius + 0.2, - 50, + CONFIG.earthRadius + CABLE_CONFIG.line.altitudeOffset, + CABLE_CONFIG.line.greatCircleSegments, ); points.push(...(i === 0 ? segment : segment.slice(1))); } @@ -273,8 +310,8 @@ export async function loadGeoJSONFromPath(scene, earthObj) { lon1, lat2, lon2, - CONFIG.earthRadius + 0.2, - 50, + CONFIG.earthRadius + CABLE_CONFIG.line.altitudeOffset, + CABLE_CONFIG.line.greatCircleSegments, ); points.push(...(i === 0 ? segment : segment.slice(1))); } @@ -324,7 +361,11 @@ export async function loadLandingPoints(scene, earthObj) { clearLandingPoints(earthObj); - const sphereGeometry = new THREE.SphereGeometry(0.4, 16, 16); + const sphereGeometry = new THREE.SphereGeometry( + CABLE_CONFIG.landingPoint.radius, + CABLE_CONFIG.landingPoint.widthSegments, + CABLE_CONFIG.landingPoint.heightSegments, + ); let validCount = 0; try { @@ -345,7 +386,11 @@ export async function loadLandingPoints(scene, earthObj) { continue; } - const position = latLonToVector3(lat, lon, CONFIG.earthRadius + 0.1); + const position = latLonToVector3( + lat, + lon, + CONFIG.earthRadius + CABLE_CONFIG.landingPoint.altitudeOffset, + ); if ( Number.isNaN(position.x) || Number.isNaN(position.y) || @@ -357,11 +402,11 @@ export async function loadLandingPoints(scene, earthObj) { const sphere = new THREE.Mesh( sphereGeometry.clone(), new THREE.MeshStandardMaterial({ - color: 0xffaa00, - emissive: 0x442200, - emissiveIntensity: 0.5, + color: CABLE_CONFIG.landingPoint.color, + emissive: CABLE_CONFIG.landingPoint.emissive, + emissiveIntensity: CABLE_CONFIG.landingPoint.emissiveIntensity, transparent: true, - opacity: 1, + opacity: CABLE_CONFIG.landingPoint.opacity, }), ); sphere.position.copy(position); @@ -371,6 +416,7 @@ export async function loadLandingPoints(scene, earthObj) { cableNames: properties.cable_names || [], country: properties.country || "未知国家", status: properties.status || "Unknown", + baseScale: CABLE_CONFIG.landingPoint.baseScale, }; earthObj.add(sphere); @@ -493,9 +539,10 @@ export function getAllLandingPoints() { return landingPoints; } -export function applyLandingPointVisualState(lockedCableName, dimAll = false) { - const pulse = (Math.sin(Date.now() * 0.003) + 1) * 0.5; - const brightness = 0.3; +export function applyLandingPointVisualState(lockedCableName, dimAll = false, camera = null) { + const pulse = + (Math.sin(Date.now() * CABLE_CONFIG.landingPointVisual.pulseSpeed) + 1) * 0.5; + const brightness = CABLE_CONFIG.landingPointVisual.dimBrightness; const relatedNames = Array.isArray(lockedCableName) ? lockedCableName.filter(Boolean) : lockedCableName @@ -509,30 +556,48 @@ export function applyLandingPointVisualState(lockedCableName, dimAll = false) { lp.userData.cableNames.some((name) => relatedNames.includes(name)); if (isRelated) { - lp.material.color.setHex(0xffaa00); - lp.material.emissive.setHex(0x442200); - lp.material.emissiveIntensity = 0.5 + pulse * 0.5; - lp.material.opacity = 0.8 + pulse * 0.2; - lp.scale.setScalar(1.2 + pulse * 0.3); + lp.material.color.setHex(CABLE_CONFIG.landingPoint.color); + lp.material.emissive.setHex(CABLE_CONFIG.landingPoint.emissive); + lp.material.emissiveIntensity = + CABLE_CONFIG.landingPointVisual.related.emissiveIntensityBase + + pulse * CABLE_CONFIG.landingPointVisual.related.emissiveIntensityPulse; + lp.material.opacity = + CABLE_CONFIG.landingPointVisual.related.opacityBase + + pulse * CABLE_CONFIG.landingPointVisual.related.opacityPulse; + const distanceScale = getLandingPointDistanceScale(lp, camera); + const baseScale = lp.userData?.baseScale || CABLE_CONFIG.landingPoint.baseScale; + lp.scale.setScalar( + (CABLE_CONFIG.landingPointVisual.related.scaleBase + + pulse * CABLE_CONFIG.landingPointVisual.related.scalePulse) * + baseScale * + distanceScale, + ); } else { - const r = 255 * brightness; - const g = 170 * brightness; - lp.material.color.setRGB(r / 255, g / 255, 0); - lp.material.emissive.setHex(0x000000); - lp.material.emissiveIntensity = 0; - lp.material.opacity = 0.3; - lp.scale.setScalar(1.0); + const dimColor = CABLE_CONFIG.landingPointVisual.dimmed.colorRGB; + const r = dimColor.r * brightness; + const g = dimColor.g * brightness; + const b = dimColor.b * brightness; + lp.material.color.setRGB(r / 255, g / 255, b / 255); + lp.material.emissive.setHex(CABLE_CONFIG.landingPointVisual.dimmed.emissive); + lp.material.emissiveIntensity = + CABLE_CONFIG.landingPointVisual.dimmed.emissiveIntensity; + lp.material.opacity = CABLE_CONFIG.landingPointVisual.dimmed.opacity; + const distanceScale = getLandingPointDistanceScale(lp, camera); + const baseScale = lp.userData?.baseScale || CABLE_CONFIG.landingPoint.baseScale; + lp.scale.setScalar(baseScale * distanceScale); } }); } -export function resetLandingPointVisualState() { +export function resetLandingPointVisualState(camera = null) { landingPoints.forEach((lp) => { - lp.material.color.setHex(0xffaa00); - lp.material.emissive.setHex(0x442200); - lp.material.emissiveIntensity = 0.5; - lp.material.opacity = 1.0; - lp.scale.setScalar(1.0); + lp.material.color.setHex(CABLE_CONFIG.landingPoint.color); + lp.material.emissive.setHex(CABLE_CONFIG.landingPoint.emissive); + lp.material.emissiveIntensity = CABLE_CONFIG.landingPoint.emissiveIntensity; + lp.material.opacity = CABLE_CONFIG.landingPoint.opacity; + const distanceScale = getLandingPointDistanceScale(lp, camera); + const baseScale = lp.userData?.baseScale || CABLE_CONFIG.landingPoint.baseScale; + lp.scale.setScalar(baseScale * distanceScale); }); } diff --git a/frontend/public/earth/js/constants.js b/frontend/public/earth/js/constants.js index 9dbd373b..9b93ca42 100644 --- a/frontend/public/earth/js/constants.js +++ b/frontend/public/earth/js/constants.js @@ -45,7 +45,50 @@ export const CABLE_CONFIG = { otherOpacity: 0.5, otherBrightness: 0.6, pulseSpeed: 0.008, - pulseCoefficient: 0.4 + pulseCoefficient: 0.4, + line: { + altitudeOffset: 0.2, + greatCircleSegments: 50, + nearPointThreshold: 0.01, + lineWidth: 1, + opacity: 1.0, + renderOrder: 1, + }, + landingPoint: { + altitudeOffset: 0.1, + radius: 0.4, + widthSegments: 16, + heightSegments: 16, + baseScale: 2.5, + color: 0xffaa00, + emissive: 0x442200, + emissiveIntensity: 0.5, + opacity: 1.0, + }, + landingPointSizeStabilization: { + enabled: true, + referenceFov: 75, + min: 0.12, + max: 3.0, + }, + landingPointVisual: { + pulseSpeed: 0.003, + dimBrightness: 0.3, + related: { + emissiveIntensityBase: 0.5, + emissiveIntensityPulse: 0.5, + opacityBase: 0.8, + opacityPulse: 0.2, + scaleBase: 1.2, + scalePulse: 0.3, + }, + dimmed: { + colorRGB: { r: 255, g: 170, b: 0 }, + emissive: 0x000000, + emissiveIntensity: 0, + opacity: 0.3, + }, + }, }; export const CABLE_STATE = { @@ -75,15 +118,21 @@ export const BGP_CONFIG = { maxRenderedMarkers: 200, altitudeOffset: 2.1, collectorAltitudeOffset: 1.6, - baseScale: 6.2, - collectorScale: 7.4, - hoverScale: 1.16, - dimmedScale: 0.92, - pulseSpeed: 0.0045, - collectorPulseSpeed: 0.0024, + marker: { + eventBaseScale: 6.2, + collectorBaseScale: 7.4, + hoverScale: 1.16, + dimmedScale: 0.92, + collectorStatusCoreBaseScale: 0.12, + collectorStatusCoreMinScale: 1.2, + }, + pulse: { + eventSpeed: 0.0045, + collectorSpeed: 0.0024, + normalAmplitude: 0.03, + lockedAmplitude: 0.16, + }, regionScale: 11.5, - normalPulseAmplitude: 0.08, - lockedPulseAmplitude: 0.28, opacity: { normal: 0.78, hover: 1.0, @@ -132,13 +181,24 @@ export const BGP_CONFIG = { dimmedColor: 0x7d8ca3, }, regionColor: 0x2dd4bf, - eventRingScaleA: 2.5, - eventRingScaleB: 3.4, - eventRingOpacity: 0.5, - eventRingSpeed: 0.001, - collectorHaloScale: 11.5, - collectorPulseHaloScale: 16.5, - collectorCoverageHaloScale: 22.5 + ring: { + scaleA: 2.5, + scaleB: 3.4, + opacity: 0.5, + speed: 0.001, + }, + halo: { + collectorScale: 11.5, + collectorPulseScale: 16.5, + collectorCoverageScale: 22.5, + }, + sizeStabilization: { + enabled: true, + collectorMin: 0.12, + collectorMax: 3.0, + eventMin: 0.12, + eventMax: 3.0, + }, }; export const PREDICTED_ORBIT_CONFIG = { diff --git a/frontend/public/earth/js/main.js b/frontend/public/earth/js/main.js index 0e28a941..dabe1c76 100644 --- a/frontend/public/earth/js/main.js +++ b/frontend/public/earth/js/main.js @@ -1537,11 +1537,11 @@ function animate() { updateBGPVisualState(lockedObjectType, lockedObject, camera); if (lockedObjectType === "cable" && lockedObject) { - applyLandingPointVisualState(lockedObject.userData.name, false); + applyLandingPointVisualState(lockedObject.userData.name, false, camera); } else if ( lockedObjectType === "satellite" && lockedSatellite ) { - applyLandingPointVisualState(null, true); + applyLandingPointVisualState(null, true, camera); } else if (lockedObjectType === "bgp" && lockedObject) { const relatedCableNames = getBGPRelatedCableNames(lockedObject); clearAllCableStates(); @@ -1555,12 +1555,13 @@ function animate() { applyLandingPointVisualState( relatedCableNames.length > 0 ? relatedCableNames : null, relatedCableNames.length === 0, + camera, ); } else if (lockedObjectType === "bgp_collector" && lockedObject) { clearAllCableStates(); - resetLandingPointVisualState(); + resetLandingPointVisualState(camera); } else { - resetLandingPointVisualState(); + resetLandingPointVisualState(camera); } updateSatellitePositions(deltaTime); diff --git a/pyproject.toml b/pyproject.toml index 2c0fcdeb..abfea286 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "planet" -version = "0.22.11" +version = "0.22.12" description = "智能星球计划 - 态势感知系统" requires-python = ">=3.14" dependencies = [ diff --git a/uv.lock b/uv.lock index 6e9e7cae..6463cd15 100644 --- a/uv.lock +++ b/uv.lock @@ -475,7 +475,7 @@ wheels = [ [[package]] name = "planet" -version = "0.22.11" +version = "0.22.12" source = { virtual = "." } dependencies = [ { name = "aiofiles" },