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} */ 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