177 lines
6.1 KiB
JavaScript
177 lines
6.1 KiB
JavaScript
import { ref } from 'vue'
|
||
import * as Cesium from 'cesium'
|
||
import { getModelCompareConfig } from '../config/modelCompare.config'
|
||
|
||
/**
|
||
* 3D Tiles加载管理 Composable
|
||
* 负责加载灾前/灾后3D模型数据,并支持分屏对比功能
|
||
*/
|
||
export function use3DTiles() {
|
||
const beforeTileset = ref(null)
|
||
const afterTileset = ref(null)
|
||
|
||
/**
|
||
* 加载3D Tileset
|
||
* @param {Cesium.Viewer} viewer - Cesium Viewer 实例
|
||
* @param {string} sceneType - 场景类型:'before' 或 'after'
|
||
* @param {boolean} autoZoom - 是否自动缩放到模型(默认false,保持用户设置的相机位置)
|
||
* @param {Cesium.SplitDirection} splitDirection - 分割方向(默认为 NONE)
|
||
*/
|
||
const load3DTileset = async (viewer, sceneType = 'after', autoZoom = false, splitDirection = Cesium.SplitDirection.NONE) => {
|
||
if (!viewer) return null
|
||
|
||
try {
|
||
console.log(`[use3DTiles] 正在加载${sceneType === 'after' ? '灾后' : '灾前'}3D模型...`)
|
||
|
||
// 从配置中获取 URL
|
||
const config = getModelCompareConfig()
|
||
const tilesetConfig = sceneType === 'after' ? config.after3DTiles : config.before3DTiles
|
||
const url = tilesetConfig.url
|
||
|
||
const tileset = await Cesium.Cesium3DTileset.fromUrl(url, {
|
||
skipLevelOfDetail: true,
|
||
baseScreenSpaceError: 1024,
|
||
skipScreenSpaceErrorFactor: 16,
|
||
skipLevels: 1,
|
||
immediatelyLoadDesiredLevelOfDetail: false,
|
||
loadSiblings: false,
|
||
maximumScreenSpaceError: 2.0, // 降低到2.0以提高模型精细度(原来是16.0)
|
||
dynamicScreenSpaceError: true, // 启用动态屏幕空间误差调整
|
||
dynamicScreenSpaceErrorDensity: 0.00278, // 启用密度调整
|
||
dynamicScreenSpaceErrorFactor: 4.0, // 动态因子
|
||
foveatedScreenSpaceError: true, // 启用视锥细化
|
||
foveatedConeSize: 0.1, // 视锥大小
|
||
foveatedMinimumScreenSpaceErrorRelaxation: 0.0 // 最小放松
|
||
})
|
||
|
||
// 将tileset添加到viewer的primitives
|
||
viewer.scene.primitives.add(tileset)
|
||
|
||
// 设置splitDirection(用于对比模式)
|
||
tileset.splitDirection = splitDirection
|
||
|
||
if (sceneType === 'after') {
|
||
afterTileset.value = tileset
|
||
} else {
|
||
beforeTileset.value = tileset
|
||
}
|
||
|
||
console.log(`[use3DTiles] ${sceneType === 'after' ? '灾后' : '灾前'}3D模型加载成功,splitDirection: ${splitDirection}`)
|
||
|
||
// 只有明确要求时才自动缩放到tileset
|
||
if (autoZoom) {
|
||
await viewer.zoomTo(tileset)
|
||
}
|
||
|
||
return tileset
|
||
} catch (error) {
|
||
console.error(`[use3DTiles] 加载${sceneType === 'after' ? '灾后' : '灾前'}3D模型失败:`, error)
|
||
return null
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 等待 Tileset 完全就绪(包括首批瓦片加载)
|
||
*
|
||
* 这个函数确保:
|
||
* 1. Tileset 本身已就绪(readyPromise)
|
||
* 2. 初始视图的所有瓦片已加载完成(initialTilesLoaded)
|
||
*
|
||
* @param {Cesium.Cesium3DTileset} tileset - 要等待的 Tileset
|
||
* @param {number} timeout - 超时时间(毫秒),默认10秒
|
||
* @returns {Promise<void>}
|
||
*/
|
||
const waitForTilesetReady = async (tileset, timeout = 10000) => {
|
||
if (!tileset) {
|
||
console.warn('[use3DTiles] waitForTilesetReady: tileset 为空')
|
||
return
|
||
}
|
||
|
||
try {
|
||
// 步骤1:等待 Tileset 基础就绪
|
||
await tileset.readyPromise
|
||
console.log('[use3DTiles] Tileset readyPromise 已完成')
|
||
|
||
// 步骤2:等待初始瓦片加载完成(带超时)
|
||
console.log('[use3DTiles] 等待初始瓦片加载...')
|
||
|
||
await Promise.race([
|
||
// 等待initialTilesLoaded事件
|
||
new Promise((resolve) => {
|
||
const handleInitialTilesLoaded = () => {
|
||
console.log('[use3DTiles] 初始瓦片加载完成(事件触发)')
|
||
tileset.initialTilesLoaded.removeEventListener(handleInitialTilesLoaded)
|
||
resolve()
|
||
}
|
||
|
||
tileset.initialTilesLoaded.addEventListener(handleInitialTilesLoaded)
|
||
|
||
// 如果已经加载完成,可能事件已经触发过了,直接resolve
|
||
// 通过检查tileset.tilesLoaded来判断
|
||
if (tileset.tilesLoaded) {
|
||
console.log('[use3DTiles] 瓦片已加载,直接继续')
|
||
tileset.initialTilesLoaded.removeEventListener(handleInitialTilesLoaded)
|
||
resolve()
|
||
}
|
||
}),
|
||
// 超时机制
|
||
new Promise((resolve) => {
|
||
setTimeout(() => {
|
||
console.warn(`[use3DTiles] 等待瓦片加载超时(${timeout}ms),继续执行`)
|
||
resolve()
|
||
}, timeout)
|
||
})
|
||
])
|
||
|
||
console.log('[use3DTiles] Tileset 已完全就绪')
|
||
} catch (error) {
|
||
console.error('[use3DTiles] 等待 Tileset 就绪失败:', error)
|
||
// 即使失败也不抛出异常,允许程序继续执行
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 移除3D Tileset
|
||
*/
|
||
const remove3DTileset = (viewer, sceneType) => {
|
||
if (!viewer) return
|
||
|
||
const tileset = sceneType === 'after' ? afterTileset.value : beforeTileset.value
|
||
if (tileset) {
|
||
viewer.scene.primitives.remove(tileset)
|
||
if (sceneType === 'after') {
|
||
afterTileset.value = null
|
||
} else {
|
||
beforeTileset.value = null
|
||
}
|
||
console.log(`[use3DTiles] ${sceneType === 'after' ? '灾后' : '灾前'}3D模型已移除`)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新 Tileset 的 splitDirection
|
||
* @param {string} sceneType - 场景类型:'before' 或 'after'
|
||
* @param {Cesium.SplitDirection} splitDirection - 新的分割方向
|
||
*/
|
||
const updateTilesetSplitDirection = (sceneType, splitDirection) => {
|
||
const tileset = sceneType === 'after' ? afterTileset.value : beforeTileset.value
|
||
if (tileset) {
|
||
tileset.splitDirection = splitDirection
|
||
console.log(`[use3DTiles] ${sceneType === 'after' ? '灾后' : '灾前'}3D模型 splitDirection 已更新为: ${splitDirection}`)
|
||
} else {
|
||
console.warn(`[use3DTiles] ${sceneType === 'after' ? '灾后' : '灾前'}3D模型不存在,无法更新 splitDirection`)
|
||
}
|
||
}
|
||
|
||
return {
|
||
beforeTileset,
|
||
afterTileset,
|
||
load3DTileset,
|
||
waitForTilesetReady,
|
||
remove3DTileset,
|
||
updateTilesetSplitDirection
|
||
}
|
||
}
|
||
|
||
export default use3DTiles
|