fix: preserve BGP event halo at globe limb
This commit is contained in:
@@ -98,7 +98,7 @@ 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; flat BGP change-event depth testing stays unchanged.
|
||||
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.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@@ -100,7 +100,7 @@ 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 事件圆环同样按锚点做整块前后判断,前面锚点的圆环关闭逐像素深度测试并置于事件图标邻近前景层,避免圆环边缘被地球切断;背面锚点整圈隐藏。
|
||||
|
||||
|
||||
聚合投影层 `.map-clusters` 的 z-index 为 8,低于 HUD;以真实地表锚点做整标遮挡。普通点仍使用原 Three.js 图层,过渡中的成员只在投影层显示一次,避免双图标。Spiderfy 引线有透明加宽命中区域,悬停联动高亮引线与子点,点击返回同一业务对象;滚轮在图标与引线上继续缩放地球。
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
SURFACE_AVOIDANCE_PROFILES,
|
||||
} from "./interactable.js";
|
||||
import { getSurfaceMarkerCameraScale, latLonToVector3 } from "./utils.js";
|
||||
import { isSurfaceAnchorVisible } from "./surface-marker-occlusion.js";
|
||||
|
||||
const bgpGroup = new THREE.Group();
|
||||
const bgpOverlayGroup = new THREE.Group();
|
||||
@@ -32,6 +33,7 @@ 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;
|
||||
@@ -42,6 +44,7 @@ const BGP_COLLECTOR_ICON_SOURCE = new URL("../assets/icons/bgp-broadcast-pin.svg
|
||||
const BGP_COLLECTOR_HOVER_SCALE = 1.08;
|
||||
const BGP_COLLECTOR_LOCKED_SCALE = 1.12;
|
||||
const BGP_COLLECTOR_PULSE_AMPLITUDE = 0.14;
|
||||
const bgpCameraLocalScratch = new THREE.Vector3();
|
||||
|
||||
bgpOverlayGroup.name = "bgp-overlay-root";
|
||||
bgpEventOverlayGroup.name = "bgp-event-overlay-layer";
|
||||
@@ -1119,12 +1122,16 @@ function createEventRingSprite(marker) {
|
||||
transparent: true,
|
||||
opacity: 0,
|
||||
depthWrite: false,
|
||||
depthTest: true,
|
||||
// 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,
|
||||
blending: THREE.AdditiveBlending,
|
||||
}),
|
||||
);
|
||||
ring.position.copy(marker.position);
|
||||
ring.renderOrder = BGP_EVENT_RENDER_ORDER - 0.05;
|
||||
ring.renderOrder = BGP_EVENT_RING_RENDER_ORDER;
|
||||
marker.userData.bgp_event_anchor_visible = true;
|
||||
ring.visible = showBGP && !marker.userData.legend_filtered && !marker.userData.icon_presentation_hidden;
|
||||
return ring;
|
||||
}
|
||||
@@ -1361,6 +1368,11 @@ 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);
|
||||
}
|
||||
updateCollectorOverlayScan(lockedObjectType, lockedObject);
|
||||
syncBGPLegendEffects();
|
||||
const hasLockedLayer = Boolean(
|
||||
@@ -1527,6 +1539,10 @@ 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));
|
||||
@@ -1540,8 +1556,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 && !marker.userData.legend_filtered && !marker.userData.icon_presentation_hidden;
|
||||
ring.renderOrder = isActive ? BGP_EVENT_RENDER_ORDER + 0.15 : BGP_EVENT_RENDER_ORDER - 0.05;
|
||||
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;
|
||||
};
|
||||
|
||||
applyRingState(marker.userData.ringA, ringPhaseA, BGP_CONFIG.ring.scaleA);
|
||||
@@ -1922,6 +1938,6 @@ export function applyBGPLegendFilter() {
|
||||
bgpCollectorIconLayer.refreshFilter();
|
||||
syncBGPLegendEffects();
|
||||
anomalyMarkers.forEach(marker => {
|
||||
if (marker.userData.ringA) marker.userData.ringA.visible = showBGP && !marker.userData.legend_filtered && !marker.userData.icon_presentation_hidden;
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user