178 lines
6.4 KiB
TypeScript
178 lines
6.4 KiB
TypeScript
import { ImportMeshAsync, ISceneLoaderProgressEvent } from '@babylonjs/core/Loading/sceneLoader';
|
||
import '@babylonjs/loaders/glTF';
|
||
import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
|
||
import { Scene } from '@babylonjs/core/scene';
|
||
import { ActionManager, ExecuteCodeAction } from '@babylonjs/core/Actions';
|
||
import { Monobehiver } from '../base/Monobehiver';
|
||
import { Dictionary } from '../utils/Dictionary';
|
||
import { AppConfig } from './AppConfig';
|
||
import { EventBridge } from '../event/bridge';
|
||
|
||
type LoadResult = {
|
||
success: boolean;
|
||
meshes?: AbstractMesh[];
|
||
skeletons?: unknown[];
|
||
error?: string;
|
||
};
|
||
|
||
/**
|
||
* 模型管理类- 负责加载、缓存和管理3D模型
|
||
*/
|
||
export class AppModel extends Monobehiver {
|
||
private modelDic: Dictionary<AbstractMesh[]>;
|
||
private loadedMeshes: AbstractMesh[];
|
||
private skeletonManager: any;
|
||
private outfitManager: any;
|
||
private isLoading: boolean;
|
||
private skeletonMerged: boolean;
|
||
|
||
constructor(mainApp: any) {
|
||
super(mainApp);
|
||
this.modelDic = new Dictionary<AbstractMesh[]>();
|
||
this.loadedMeshes = [];
|
||
this.skeletonManager = null;
|
||
this.outfitManager = null;
|
||
this.isLoading = false;
|
||
this.skeletonMerged = false;
|
||
}
|
||
|
||
/** 初始化子管理器(占位:实际实现已移除) */
|
||
initManagers(): void {
|
||
// 这里原本会初始化 SkeletonManager 和 OutfitManager,已留空以避免恢复已删除的实现
|
||
}
|
||
|
||
/** 加载配置中的所有模型 */
|
||
async loadModel(): Promise<void> {
|
||
if (!AppConfig.modelUrlList?.length || this.isLoading) return;
|
||
this.isLoading = true;
|
||
try {
|
||
const total = AppConfig.modelUrlList.length;
|
||
EventBridge.modelLoadProgress({ loaded: 0, total, urls: AppConfig.modelUrlList, progress: 0, percentage: 0 });
|
||
for (let i = 0; i < AppConfig.modelUrlList.length; i++) {
|
||
const url = AppConfig.modelUrlList[i];
|
||
const handleProgress = (event: ISceneLoaderProgressEvent): void => {
|
||
const currentProgress = event.lengthComputable && event.total > 0
|
||
? Math.min(1, event.loaded / event.total)
|
||
: 0;
|
||
const overallProgress = Math.min(1, (i + currentProgress) / total);
|
||
EventBridge.modelLoadProgress({
|
||
loaded: i + currentProgress,
|
||
total,
|
||
url,
|
||
progress: overallProgress,
|
||
percentage: Number((overallProgress * 100).toFixed(2)),
|
||
detail: {
|
||
url,
|
||
lengthComputable: event.lengthComputable,
|
||
loadedBytes: event.loaded,
|
||
totalBytes: event.total
|
||
}
|
||
});
|
||
};
|
||
|
||
const result = await this.loadSingleModel(url, handleProgress);
|
||
const overallProgress = Math.min(1, (i + 1) / total);
|
||
EventBridge.modelLoadProgress({
|
||
loaded: i + 1,
|
||
total,
|
||
url,
|
||
success: result.success,
|
||
progress: overallProgress,
|
||
percentage: Number((overallProgress * 100).toFixed(2))
|
||
});
|
||
if (!result.success) {
|
||
EventBridge.modelLoadError({ url, error: result.error });
|
||
}
|
||
}
|
||
EventBridge.modelLoaded({ urls: AppConfig.modelUrlList });
|
||
} finally {
|
||
this.isLoading = false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载单个模型
|
||
* @param modelUrl 模型URL
|
||
*/
|
||
async loadSingleModel(modelUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void): Promise<LoadResult> {
|
||
try {
|
||
const cached = this.getCachedMeshes(modelUrl);
|
||
if (cached) return { success: true, meshes: cached };
|
||
|
||
const scene: Scene | null = this.mainApp.appScene.object;
|
||
if (!scene) return { success: false, error: '场景未初始化' };
|
||
|
||
const result = await ImportMeshAsync(modelUrl, scene, { onProgress });
|
||
if (!result?.meshes?.length) return { success: false, error: '未找到网格' };
|
||
|
||
this.modelDic.Set(modelUrl, result.meshes);
|
||
this.loadedMeshes.push(...result.meshes);
|
||
return { success: true, meshes: result.meshes, skeletons: result.skeletons };
|
||
} catch (e: any) {
|
||
console.error(`模型加载失败: ${modelUrl}`, e);
|
||
return { success: false, error: e?.message };
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
/** 为网格设置阴影(投射和接收) */
|
||
setupShadows(meshes: AbstractMesh[]): void {
|
||
const appLight = this.mainApp.appLight;
|
||
if (!appLight) return;
|
||
|
||
meshes.forEach(mesh => {
|
||
if (mesh.getTotalVertices() > 0) {
|
||
appLight.addShadowCaster(mesh);
|
||
mesh.receiveShadows = true;
|
||
}
|
||
});
|
||
}
|
||
|
||
/** 获取缓存的网格 */
|
||
getCachedMeshes(url: string): AbstractMesh[] | undefined {
|
||
return this.modelDic.Get(url);
|
||
}
|
||
|
||
|
||
|
||
|
||
/** 清理所有资源 */
|
||
clean(): void {
|
||
this.modelDic.Clear();
|
||
this.loadedMeshes.forEach(m => m?.dispose());
|
||
this.loadedMeshes = [];
|
||
this.skeletonManager?.clean();
|
||
this.outfitManager?.clean();
|
||
this.isLoading = false;
|
||
this.skeletonMerged = false;
|
||
}
|
||
|
||
/**
|
||
* 销毁指定模型
|
||
* @param modelName 模型名称
|
||
*/
|
||
destroyModel(modelName: string): void {
|
||
// 遍历模型字典,查找匹配的模型
|
||
const keys = this.modelDic.Keys();
|
||
for (const key of keys) {
|
||
if (key.includes(modelName)) {
|
||
const meshes = this.modelDic.Get(key);
|
||
if (meshes) {
|
||
// 销毁所有网格
|
||
meshes.forEach(mesh => mesh?.dispose());
|
||
// 从字典中移除
|
||
this.modelDic.Remove(key);
|
||
// 从加载的网格列表中移除
|
||
this.loadedMeshes = this.loadedMeshes.filter(mesh => !meshes.includes(mesh));
|
||
console.log(`Model destroyed: ${modelName}`);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
console.warn(`Model not found: ${modelName}`);
|
||
}
|
||
|
||
}
|