308 lines
9.7 KiB
JavaScript
Raw Normal View History

import { ref, onUnmounted } from 'vue'
import * as Cesium from 'cesium'
import { use3DTiles } from './use3DTiles'
/**
* 双地图对比模式
* 使用两个独立的Cesium Viewer实现并排对比
* 左侧显示灾前场景右侧显示灾后场景
* 单向相机同步右侧主地图驱动左侧对比地图跟随
*/
export function useDualMapCompare() {
/** 左侧Viewer引用 */
const leftViewer = ref(null)
/** 右侧Viewer引用 */
const rightViewer = ref(null)
/** 对比模式是否激活 */
const isCompareMode = ref(false)
/** 相机同步监听器移除函数 */
let cameraSyncRemover = null
/** 左侧3D Tileset灾前 */
let leftTileset = null
/** 右侧3D Tileset灾后主地图的tileset */
let rightTileset = null
const { load3DTileset } = use3DTiles()
/**
* 初始化左侧Viewer灾前场景
* @param {HTMLElement} container - 容器元素
* @returns {Cesium.Viewer}
*/
const initLeftViewer = (container) => {
if (!container) {
console.error('[useDualMapCompare] 左侧容器元素不存在')
return null
}
// 验证容器尺寸
const { clientWidth, clientHeight } = container
console.log(`[useDualMapCompare] 左侧容器尺寸: ${clientWidth}x${clientHeight}`)
if (clientWidth <= 0 || clientHeight <= 0) {
console.error(`[useDualMapCompare] 左侧容器尺寸无效 (width=${clientWidth}, height=${clientHeight})`)
return null
}
// 创建左侧viewer
const viewer = new Cesium.Viewer(container, {
animation: false,
baseLayerPicker: false,
fullscreenButton: false,
geocoder: false,
homeButton: false,
infoBox: false,
sceneModePicker: false,
selectionIndicator: false,
timeline: false,
navigationHelpButton: false,
scene3DOnly: true,
shouldAnimate: false,
})
// 移除默认的Cesium logo和版权信息
viewer.cesiumWidget.creditContainer.style.display = 'none'
// 完全禁用左侧Viewer的所有交互只作为对比显示
viewer.scene.screenSpaceCameraController.enableRotate = false
viewer.scene.screenSpaceCameraController.enableTranslate = false
viewer.scene.screenSpaceCameraController.enableZoom = false
viewer.scene.screenSpaceCameraController.enableTilt = false
viewer.scene.screenSpaceCameraController.enableLook = false
leftViewer.value = viewer
console.log('[useDualMapCompare] 左侧Viewer初始化成功交互已禁用')
return viewer
}
/**
* 设置单向相机同步
* 右侧主地图驱动左侧对比地图跟随
* 使用 postRender 实现逐帧同步直接传递 position 引用以获得最佳性能
* @param {Cesium.Viewer} rightViewerInstance - 右侧Viewer主地图驱动者
* @param {Cesium.Viewer} leftViewerInstance - 左侧Viewer对比地图跟随者
*/
const setupCameraSync = (rightViewerInstance, leftViewerInstance) => {
if (!rightViewerInstance || !leftViewerInstance) {
console.warn('[useDualMapCompare] Viewer未初始化无法设置相机同步')
return
}
console.log('[useDualMapCompare] 设置单向相机同步(右→左)...')
/**
* 相机同步处理函数
* 在右侧 Viewer postRender 中调用将右侧相机状态同步到左侧
*/
const handleCameraSync = () => {
// 检查 Viewer 是否有效
if (!leftViewerInstance || leftViewerInstance.isDestroyed()) return
if (!rightViewerInstance || rightViewerInstance.isDestroyed()) return
const rightCamera = rightViewerInstance.camera
// 直接传递 position 引用(不 clone获得最佳性能
// Cesium 内部会处理拷贝,且在单向同步场景下安全
leftViewerInstance.camera.setView({
destination: rightCamera.position,
orientation: {
heading: rightCamera.heading,
pitch: rightCamera.pitch,
roll: rightCamera.roll
}
})
}
// 只在右侧 Viewer 的 postRender 中注册同步函数
// 确保右侧相机更新完成后再同步到左侧
rightViewerInstance.scene.postRender.addEventListener(handleCameraSync)
// 保存移除函数
cameraSyncRemover = () => {
rightViewerInstance.scene.postRender.removeEventListener(handleCameraSync)
console.log('[useDualMapCompare] 相机同步已移除')
}
console.log('[useDualMapCompare] 单向相机同步设置完成')
}
/**
* 启用对比模式
* @param {Cesium.Viewer} rightViewerInstance - 右侧Viewer实例主地图灾后场景
* @param {Object} options - 配置选项
* @param {boolean} options.skipLeftModelLoad - 是否跳过左侧模型加载
* @param {boolean} options.loadLeftModel - 是否仅加载左侧模型不重新初始化Viewer
*/
const enableCompareMode = async (rightViewerInstance, options = {}) => {
const { skipLeftModelLoad = false, loadLeftModel = false } = options
if (!rightViewerInstance) {
console.error('[useDualMapCompare] 右侧主地图Viewer未初始化')
return
}
// 如果只是加载左侧模型Viewer已存在
if (loadLeftModel && leftViewer.value) {
console.log('[useDualMapCompare] 加载左侧灾前模型...')
try {
const tileset = await load3DTileset(leftViewer.value, 'before', false)
if (tileset) {
leftTileset = tileset
console.log('[useDualMapCompare] 左侧灾前模型加载完成')
}
} catch (error) {
console.error('[useDualMapCompare] 左侧模型加载失败:', error)
}
return
}
console.log('[useDualMapCompare] 启用对比模式...')
rightViewer.value = rightViewerInstance
// 查找左侧容器容器已存在于DOM中
const leftContainer = document.getElementById('leftCesiumContainer')
if (!leftContainer) {
console.error('[useDualMapCompare] 找不到左侧容器元素')
return
}
// 先设置状态触发CSS动画
isCompareMode.value = true
// 等待CSS过渡完成300ms + 50ms buffer
await new Promise(resolve => setTimeout(resolve, 350))
// 初始化左侧Viewer
const leftViewerInstance = initLeftViewer(leftContainer)
if (!leftViewerInstance) {
console.error('[useDualMapCompare] 左侧Viewer初始化失败')
isCompareMode.value = false
return
}
// 立即同步右侧相机的当前位置到左侧
console.log('[useDualMapCompare] 同步初始相机位置...')
const rightCamera = rightViewerInstance.camera
leftViewerInstance.camera.setView({
destination: rightCamera.position.clone(),
orientation: {
heading: rightCamera.heading,
pitch: rightCamera.pitch,
roll: rightCamera.roll
}
})
// 设置相机同步(单向:右→左)
setupCameraSync(rightViewerInstance, leftViewerInstance)
// 根据选项决定是否加载左侧模型
if (!skipLeftModelLoad) {
// 异步加载灾前模型到左侧(不阻塞对比模式启用)
console.log('[useDualMapCompare] 开始异步加载左侧灾前模型...')
load3DTileset(leftViewerInstance, 'before', false)
.then(tileset => {
if (tileset) {
leftTileset = tileset
console.log('[useDualMapCompare] 左侧灾前模型加载完成')
}
})
.catch(error => {
console.error('[useDualMapCompare] 左侧模型加载失败:', error)
})
} else {
console.log('[useDualMapCompare] 跳过左侧模型加载(将延迟加载)')
}
// 右侧保持灾后模型(已加载)
// 触发左侧viewer resize
setTimeout(() => {
if (leftViewerInstance && leftViewerInstance.canvas) {
leftViewerInstance.resize()
leftViewerInstance.camera.changed.raiseEvent()
}
// 同时触发右侧viewer resize
if (rightViewerInstance && rightViewerInstance.canvas) {
rightViewerInstance.resize()
}
}, 350)
console.log('[useDualMapCompare] 对比模式已启用')
}
/**
* 禁用对比模式
*/
const disableCompareMode = () => {
console.log('[useDualMapCompare] 禁用对比模式...')
// 移除相机同步
if (cameraSyncRemover) {
cameraSyncRemover()
cameraSyncRemover = null
}
// 销毁左侧Viewer
if (leftViewer.value && !leftViewer.value.isDestroyed()) {
// 清理左侧tileset
if (leftTileset) {
leftViewer.value.scene.primitives.remove(leftTileset)
leftTileset = null
}
leftViewer.value.destroy()
leftViewer.value = null
console.log('[useDualMapCompare] 左侧Viewer已销毁')
}
// 触发右侧viewer resize恢复全屏
if (rightViewer.value && !rightViewer.value.isDestroyed()) {
setTimeout(() => {
if (rightViewer.value && rightViewer.value.canvas) {
rightViewer.value.resize()
}
}, 350)
}
isCompareMode.value = false
console.log('[useDualMapCompare] 对比模式已禁用')
}
/**
* 切换对比模式
* @param {boolean} active - true启用false禁用
* @param {Cesium.Viewer} rightViewerInstance - 右侧Viewer实例主地图
* @param {Object} options - 配置选项传递给 enableCompareMode
*/
const toggleCompareMode = async (active, rightViewerInstance, options) => {
if (active) {
await enableCompareMode(rightViewerInstance, options)
} else {
disableCompareMode()
}
}
// 清理
onUnmounted(() => {
disableCompareMode()
})
return {
leftViewer,
rightViewer,
isCompareMode,
enableCompareMode,
disableCompareMode,
toggleCompareMode
}
}
export default useDualMapCompare