|
|
@@ -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]
|