修改3d锚点被地形遮挡消失之后 popup也要消

This commit is contained in:
扈兆增 2026-08-05 17:42:01 +08:00
parent 7aa98abdfb
commit 66f90555ff

View File

@ -1003,10 +1003,18 @@ export class MapCesium implements MapInterface {
this.hidePopup();
}
/** 判断 3D 位置是否被地形遮挡(基于射线与 terrain 求交) */
/**
* 3D /
*
* popup
* //
* popup
* popup
*/
private isPositionBehindTerrain(position: Cesium.Cartesian3): boolean {
if (!this.viewer) return true;
const scene = this.viewer.scene;
const cameraPos = scene.camera.positionWC;
const windowCoord = Cesium.SceneTransforms.worldToWindowCoordinates(
scene,
@ -1014,19 +1022,76 @@ export class MapCesium implements MapInterface {
);
if (!windowCoord) return true; // 在地球背面
if (!scene.pickPositionSupported) {
// 降级:不支持深度拾取时回退到射线与地形求交
const ray = scene.camera.getPickRay(windowCoord);
if (!ray) return false;
const terrainPos = scene.globe.pick(ray, scene);
if (!terrainPos) return false; // 该方向无地形
const cameraPos = scene.camera.positionWC;
const terrainDist = Cesium.Cartesian3.distance(cameraPos, terrainPos);
const entityDist = Cesium.Cartesian3.distance(cameraPos, position);
// 地形交点明显更近 → 实体被遮挡
return terrainDist + 10 < entityDist;
}
// 深度缓冲拾取:读取像素处离相机最近的渲染表面(图标自身/地形/模型)的深度。
// 相比 globe.pick 射线-地形求交,深度缓冲就是实际渲染结果,与锚点可见性一致。
const posDist = Cesium.Cartesian3.distance(cameraPos, position);
const samplePoint = new Cesium.Cartesian2(0, 0);
// 该像素最近渲染表面不比锚点更近(或对天空无表面)→ 该处锚点可见
const isPixelVisible = (x: number, y: number): boolean => {
samplePoint.x = x;
samplePoint.y = y;
const picked = scene.pickPosition(samplePoint);
if (!Cesium.defined(picked)) return true;
return Cesium.Cartesian3.distance(cameraPos, picked) + 10 >= posDist;
};
// 快速路径:图标中心仍可见 → 直接判定未遮挡(最常见情况,仅 1 次拾取)
if (isPixelVisible(windowCoord.x, windowCoord.y)) return false;
// 图标中心被遮挡:继续采样整个图标范围(与碰撞检测图标碰撞盒尺寸公式一致),
// 任一采样点仍可见 → 图标还有出露部分popup 保留
const half = this.getAnchorIconHalfExtent();
const step = Math.max(4, Math.ceil(half / 2));
// 纵向:图标顶 → 底
for (let d = -half; d <= half; d += step) {
if (isPixelVisible(windowCoord.x, windowCoord.y + d)) return false;
}
// 横向:图标中心行左右边缘
if (
isPixelVisible(windowCoord.x - half, windowCoord.y) ||
isPixelVisible(windowCoord.x + half, windowCoord.y)
) {
return false;
}
// 整个图标范围均被遮挡 → 图标不可见
return true;
}
/** 锚点图标在屏幕上的近似半宽/半高px与碰撞检测中的图标碰撞盒公式一致 */
private getAnchorIconHalfExtent(): number {
const zoom = this.getCurrentCesiumZoom() || 4.5;
const iconScaleFactor = Math.max(
this.ICON_MIN_SCALE,
Math.min(
this.ICON_MAX_SCALE,
this.ICON_BASE_SCALE + (zoom - 4.5) * this.ICON_SCALE_RATE
)
);
const camHeight = this.viewer!.camera.positionCartographic.height;
const sbdT = Math.max(
0,
Math.min(1, (camHeight - 1000) / (20000000 - 1000))
);
const scaleByDist = 1.5 + sbdT * (0.8 - 1.5);
// 图标图片像素宽度 24视觉尺寸 = 24 * scale * scaleByDistance半宽约其一半
const visualIconSize = 24 * iconScaleFactor * scaleByDist;
return Math.max(7, visualIconSize / 2);
}
/** 根据实体世界坐标把 popup 定位到屏幕坐标 */
private updatePopupPosition(position: Cesium.Cartesian3): void {
if (!this.viewer || !this.popupElement) return;
@ -1110,6 +1175,10 @@ export class MapCesium implements MapInterface {
this._emptyCandidateCount = 0;
this.clearBatchPopups();
this.hidePopup(); // 清除可能残留的 hover popup
// 重新应用深度测试配置:进入批量模式时可能已有锚点图层加载完成,
// 但俯仰角未再变化导致 updateDepthTestForPitch 不会重刷,图标停留为“始终置顶”。
// 平视下必须开启深度测试(图标被地形遮挡),遮挡检测才能与实际渲染一致。
this.applyPointDepthTest(this.getDepthTestValue());
// 先注册 postRender内含 zoom 兜底检测),再构建 popup确保缩放/拖拽时始终有同步线程
this.setupBatchPopupPostRenderSync();
// 碰撞检测改为 rAF 异步执行,不再同步阻塞主线程,避免进入批量模式时卡顿 1-2s。
@ -1377,6 +1446,8 @@ export class MapCesium implements MapInterface {
const viewW = canvas.clientWidth;
const viewH = canvas.clientHeight;
const padding = 48;
// 俯视模式下锚点图标始终置顶渲染(深度测试关闭),不会被地形遮挡,跳过遮挡检测省开销
const skipOcclusionCheck = this._currentPitchMode === 'down';
// 第一遍:更新位置、收集可见项的屏幕矩形(用缓存宽高,不依赖 DOM 测量)
const visibleRects: {
@ -1442,6 +1513,17 @@ export class MapCesium implements MapInterface {
continue;
}
// 拖拽/平视时锚点被地形遮挡 → 隐藏 popup与 rebuild 候选阶段的遮挡过滤一致);
// 漫游时跳过:模型上方锚点易被新加载的高精度地形误判为遮挡
if (
!skipOcclusionCheck &&
!this._isRoaming &&
this.isPositionBehindTerrain(item.worldPosition)
) {
element.style.display = 'none';
continue;
}
const x = cp.x;
const y = cp.y;
// 通过所有检查后才设为可见(与开头的惰性策略配合,避免全量闪一下)