Files
DianZhanDemo/app/composables/powerStation/modes/useSimplifiedMode.ts
ch197511161 ddce8fce18 init5
2025-12-11 01:29:41 +08:00

220 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 简化模式 - 模型加载
* 按文件名直接加载模型,支持按需加载和自动回退
*/
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,
}
}