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