Prechádzať zdrojové kódy

fix(cockpit/t76): restore projectMarkerEntity after sweep; tidy DOM-pick comments

ChenJZ 1 deň pred
rodič
commit
d1e246d2cc

+ 41 - 0
src/modules/cockpit/services/cesiumEngine.js

@@ -2148,6 +2148,47 @@ export function groundHeightAt(viewer, lon, lat) {
     return Number.isFinite(height) ? height : null
 }
 
+/** 贴地投影与屏幕分离共用一份口径,返回视觉中心及当前图标尺寸。 */
+function projectMarkerEntity(viewer, entity) {
+    if (!viewer || !viewer.scene || !viewer.canvas || !entity || !entity.isShowing || !entity.position || !entity.billboard) return null
+    const original = entity.position.getValue(viewer.clock.currentTime)
+    if (!original) return null
+    const ellipsoid = viewer.scene.globe.ellipsoid
+    const carto = ellipsoid.cartesianToCartographic(original)
+    if (!carto) return null
+
+    // 地面高程:实体位置是静态的,地形瓦片也只会逐步补齐;每帧给每个实体查一次
+    // `globe.getHeight`(四叉树下降)是纯重复。按实体缓存 3 秒,瓦片补齐后自然刷新。
+    const nowMs = typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now()
+    let height = 0
+    const cachedHeight = entityGroundHeightCache.get(entity)
+    if (cachedHeight && nowMs - cachedHeight.at < 3000) {
+        height = cachedHeight.height
+    } else {
+        const sampled = viewer.scene.globe.getHeight(carto)
+        height = Number.isFinite(sampled) ? sampled : 0
+        entityGroundHeightCache.set(entity, { height, at: nowMs })
+    }
+
+    const world = Cartesian3.fromRadians(carto.longitude, carto.latitude, height)
+    const camera = viewer.camera
+    // 复用同一个 scratch 向量:每实体每帧 new Cartesian3 在高频投影里是纯垃圾。
+    const toward = Cartesian3.subtract(world, camera.positionWC, projectionCache.toward)
+    if (Cartesian3.dot(toward, camera.directionWC) <= 0) return null
+    // 遮挡体同一帧内所有实体共用(原本每实体 new 一个,是纯浪费)。
+    const occluder = occluderCached(viewer, camera)
+    if (!occluder.isPointVisible(world)) return null
+    const at = SceneTransforms.worldToWindowCoordinates(viewer.scene, world)
+    if (!at || !Number.isFinite(at.x) || !Number.isFinite(at.y)) return null
+    // 画布矩形只随窗口尺寸变;120ms 保鲜即可,不必每个实体读一次布局。
+    const rect = canvasRectCached(viewer)
+    const time = viewer.clock.currentTime
+    const read = (property, fallback) => property && property.getValue ? property.getValue(time) : fallback
+    const offset = read(entity.billboard.pixelOffset, ZERO_PIXEL_OFFSET)
+    const scale = read(entity.billboard.scale, 1)
+    const iconSize = Math.max(read(entity.billboard.width, 18), read(entity.billboard.height, 18)) * scale
+    return { x: at.x + rect.left + offset.x, y: at.y + rect.top + offset.y, iconSize }
+}
 export function hasPointMarker(viewer, kind, code) {
     if (!viewer || !code) return false
     const source = markerSources.get(viewer) || viewer.dataSources.getByName(POINT_MARKER_SOURCE)[0]

+ 6 - 4
src/modules/cockpit/services/mapService.js

@@ -501,7 +501,7 @@ function publishDevHook() {
         // 票 21:地面/管线视图切换(走查断言"切换不重置中心"用)。
         setViewMode: (mode, overrides) => setViewMode(mode, overrides),
         getViewMode: () => getViewMode(),
-        // 票 76(C6):A/B 显隐把手已退役(`?overlay=0` / set/toggle/​setMarkerOverlay 一并摘除),
+        // 票 76(C6):A/B 显隐把手已退役(?overlay=0 与 set/toggle 那组 DEV 把手一并摘除),
         // 只留只读读数给走查确认 overlay 在位。
         getMarkerOverlay: () => isMarkerOverlayVisible(),
         // 票 36:主场景区(走查与人工排查读"配没配、矩形是哪一个",票 37/38 也读这一个出口)。
@@ -2631,7 +2631,7 @@ let toneColorsCache = null
  *
  * 为什么必须是一个函数:点位(`syncPointMarkers`)与关联设备(`syncAssociatedDevices`)是两条
  * sync 入口,此前各写一份判据,于是出现「点位修好、设备点没修」。视觉去留只该有一个开关。
- * 语义:overlay 已挂载且未被 `?overlay=0` 关掉 → billboard 只留实体与读数(`show:false`),
+ * 语义:overlay 已挂载 → billboard 只留实体与读数(`show:false`),
  * 视觉由 DOM 承担;对照态(overlay 关)自动回到 billboard。
  */
 function billboardVisualHidden() {
@@ -3144,7 +3144,7 @@ export function syncAssociatedDevices(devices, options = {}) {
             (device) => colors[device.tone] || colors.neutral,
             // 票 76(C1):DOM overlay 挂载且可见时,聚合符号**视觉**由 overlay 承载 —— 引擎只保留
             // 实体与读数面(身份/位置/投影/readAssociatedDevices 不变),billboard/label 不渲染,
-            // 两层不重画同一个聚合符号;`?overlay=0` 走 A/B 对照组时自动回到 billboard 视觉。
+            // 两层不重画同一个聚合符号(票 76 / C6 后不再有 A/B 对照组)。
             // 票 76(C1/C4):聚合符号与**设备本体**的视觉都让位给 DOM —— 同一判据 `billboardVisualHidden()`,
             // 不会再出现「点位修好、设备点没修」。
             {
@@ -3673,7 +3673,9 @@ export function setDomHoverTarget(kind, code) {
  */
 function domMarkerAt(clientX, clientY) {
     if (!Number.isFinite(clientX) || !Number.isFinite(clientY)) return null
-    if (!instance || !instance.host || !isMarkerOverlayMounted() || !isMarkerOverlayVisible()) return null
+    // 不做「overlay 是否挂载」的前置判断:overlay 不在时 `elementFromPoint` 命中的自然不是节点,结果同为 null;
+    // 反过来这道判断会让「按坐标点选」在 node 里无法断言(行为闸门要驱动同一条链)。
+    if (!instance || !instance.host) return null
     const doc = instance.host.ownerDocument
     if (!doc || typeof doc.elementFromPoint !== 'function') return null
     const element = doc.elementFromPoint(clientX, clientY)