修改3d锚点被地形遮挡消失之后 popup也要消
This commit is contained in:
parent
7aa98abdfb
commit
66f90555ff
@ -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,17 +1022,74 @@ export class MapCesium implements MapInterface {
|
||||
);
|
||||
if (!windowCoord) return true; // 在地球背面
|
||||
|
||||
const ray = scene.camera.getPickRay(windowCoord);
|
||||
if (!ray) return false;
|
||||
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 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;
|
||||
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 定位到屏幕坐标 */
|
||||
@ -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;
|
||||
// 通过所有检查后才设为可见(与开头的惰性策略配合,避免全量闪一下)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user