/** * 简化模式 - 模型加载 * 按文件名直接加载模型,支持按需加载和自动回退 */ import { ElMessage } from 'element-plus' import type * as THREE from 'three' import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' import { markRaw } from 'vue' import type { ModelModeContext } from '../types' // ============================================================ // 配置常量 // ============================================================ /** DRACO解码器路径 */ const DRACO_DECODER_PATH = 'https://www.gstatic.com/draco/v1/decoders/' /** 简化模式配置 */ const SIMPLIFIED_MODE_CONFIG = { /** 层级显示的最大深度 */ maxHierarchyDepth: 2, /** 模型文件扩展名 */ fileExtension: '.glb', } as const // ============================================================ // 主钩子函数 // ============================================================ /** * 简化模式组合式函数 * @param context - 模型模式上下文 */ export function useSimplifiedMode(context: ModelModeContext) { const { store, isLoading, archetypeModel, modelGroup, objectMap, modelCache, replaceWithPBRMaterial, generateHierarchy, initEnvironment, applyLightingMode, fitView, rebuildObjectMap, generateBreadcrumbs, } = context // ============================================================ // 模型设置 // ============================================================ /** * 从缓存设置模型 * @param model - 缓存的模型 * @param fileName - 文件名 */ const setupModelFromCache = (model: THREE.Object3D, fileName: string) => { const clonedModel = model.clone() const nameWithoutExt = fileName.replace(SIMPLIFIED_MODE_CONFIG.fileExtension, '') // 设置模型名称 clonedModel.name = nameWithoutExt // 更新原型模型 archetypeModel.value = markRaw(clonedModel.clone()) // 清空并添加到模型组 modelGroup.clear() modelGroup.add(clonedModel) // 生成层级结构(只显示指定深度的节点) let hierarchy = [] if (clonedModel.children.length > 0) { // 如果模型有子节点,将每个子节点作为一级节点 clonedModel.children.forEach((child, index) => { const childPath = index === 0 ? nameWithoutExt : `${nameWithoutExt}~child${index}` const childHierarchy = generateHierarchy( child, childPath, 1, SIMPLIFIED_MODE_CONFIG.maxHierarchyDepth ) hierarchy.push(childHierarchy) }) } else { // 如果模型没有子节点,使用模型本身作为一级节点 const modelHierarchy = generateHierarchy( clonedModel, nameWithoutExt, 1, SIMPLIFIED_MODE_CONFIG.maxHierarchyDepth ) hierarchy = [modelHierarchy] } store.modelHierarchy = hierarchy // 生成面包屑(返回供调用者处理) const breadcrumbs = generateBreadcrumbs(nameWithoutExt) // 重建物体映射 objectMap.clear() rebuildObjectMap(clonedModel) // 初始化环境和光照 initEnvironment() applyLightingMode() // 适配视图 fitView(clonedModel) } // ============================================================ // 模型加载 // ============================================================ /** * 根据命名约定构建模型URL * @param fileName - 文件名 * @returns 完整URL路径 */ const buildModelUrl = (fileName: string): string => { // 格式: Root~Parent~Child.glb -> /3DModels/Root/Root~Parent~Child.glb const nameWithoutExt = fileName.slice(0, -4) const rootFolder = nameWithoutExt.split('~')[0] return `/3DModels/${rootFolder}/${fileName}` } /** * 按文件名加载模型 * @param fileName - 文件名(可带或不带.glb扩展名) * @param onLoaded - 加载完成回调 */ const loadModelByFileName = (fileName: string, onLoaded?: () => void) => { // 确保有.glb扩展名 if (!fileName.endsWith(SIMPLIFIED_MODE_CONFIG.fileExtension)) { fileName += SIMPLIFIED_MODE_CONFIG.fileExtension } // 检查缓存 if (store.modelCacheEnabled && modelCache.has(fileName)) { const cached = modelCache.get(fileName)! setupModelFromCache(cached, fileName) if (onLoaded) onLoaded() return } // 开始加载 isLoading.value = true store.isModelLoading = true const url = buildModelUrl(fileName) console.log(`从 ${url} 加载模型`) // 创建加载器 const loader = new GLTFLoader() const dracoLoader = new DRACOLoader() dracoLoader.setDecoderPath(DRACO_DECODER_PATH) loader.setDRACOLoader(dracoLoader) loader.load( url, gltf => { const rawModel = gltf.scene replaceWithPBRMaterial(rawModel) // 缓存模型 if (store.modelCacheEnabled) { modelCache.set(fileName, rawModel.clone()) } // 设置模型 setupModelFromCache(rawModel, fileName) isLoading.value = false store.isModelLoading = false dracoLoader.dispose() if (onLoaded) onLoaded() }, undefined, error => { console.warn('加载模型失败:', fileName, error) ElMessage.error(`模型加载失败: ${fileName},请检查网络连接或模型文件是否存在`) // 回退逻辑:尝试加载父级模型 const nameWithoutExt = fileName.slice(0, -4) const parts = nameWithoutExt.split('~') if (parts.length > 1) { // 移除最后一级,尝试加载父级 parts.pop() const parentName = parts.join('~') console.log('回退到父级:', parentName) loadModelByFileName(parentName, onLoaded) } else { // 没有父级可回退 console.error('没有父级可以回退,或根节点加载失败。') isLoading.value = false store.isModelLoading = false dracoLoader.dispose() } } ) } // ============================================================ // 导出接口 // ============================================================ return { /** 按文件名加载模型 */ loadModelByFileName, } }