This commit is contained in:
2026-04-24 19:17:31 +08:00
parent 6c94559383
commit 01fdc0ee37
18 changed files with 696 additions and 196 deletions

View File

@ -2,7 +2,6 @@ import { ImportMeshAsync, ISceneLoaderProgressEvent } from '@babylonjs/core/Load
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';
@ -15,75 +14,37 @@ type LoadResult = {
error?: string;
};
type ModelConfig = {
name: string;
url: string;
};
/**
* 模型管理类- 负责加载、缓存和管理3D模型
* 模型管理类 - 负责加载、缓存和管理3D模型
*/
export class AppModel extends Monobehiver {
private modelDic: Dictionary<AbstractMesh>;
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.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 });
}
}
await this.loadMultipleModels(AppConfig.modelUrlList);
EventBridge.modelLoaded({ urls: AppConfig.modelUrlList });
} finally {
this.isLoading = false;
@ -91,19 +52,74 @@ export class AppModel extends Monobehiver {
}
/**
* 加载单个模型
* @param modelUrl 模型URL
* 批量加载模型(内部方法)
* @param urls 模型URL数组
*/
async loadSingleModel(modelUrl: string, onProgress?: (event: ISceneLoaderProgressEvent) => void): Promise<LoadResult> {
private async loadMultipleModels(urls: string[]): Promise<void> {
const total = urls.length;
EventBridge.modelLoadProgress({ loaded: 0, total, urls, progress: 0, percentage: 0 });
for (let i = 0; i < urls.length; i++) {
const url = urls[i];
const result = await this.loadSingleModel(url, (event) => {
this.emitProgress(i, total, url, event);
});
this.emitProgress(i + 1, total, url, null, result.success);
if (!result.success) {
EventBridge.modelLoadError({ url, error: result.error });
}
}
}
/**
* 发送加载进度事件
*/
private emitProgress(
loaded: number,
total: number,
url: string,
event: ISceneLoaderProgressEvent | null,
success?: boolean
): void {
const currentProgress = event?.lengthComputable && event.total > 0
? Math.min(1, event.loaded / event.total)
: 0;
const overallProgress = Math.min(1, (loaded + (event ? currentProgress : 0)) / total);
EventBridge.modelLoadProgress({
loaded: loaded + (event ? currentProgress : 0),
total,
url,
success,
progress: overallProgress,
percentage: Number((overallProgress * 100).toFixed(2)),
detail: event ? {
url,
lengthComputable: event.lengthComputable,
loadedBytes: event.loaded,
totalBytes: event.total
} : undefined
});
}
/**
* 加载单个模型文件
* @param modelUrl 模型URL
* @param onProgress 进度回调
*/
private async loadSingleModel(
modelUrl: string,
onProgress?: (event: ISceneLoaderProgressEvent) => void
): Promise<LoadResult> {
try {
const scene: Scene | null = this.mainApp.appScene.object;
const scene = 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: '未找到网格' };
// 存储根网格(通常是第一个网格)
const rootMesh = result.meshes[0];
this.loadedMeshes.push(...result.meshes);
return { success: true, meshes: result.meshes, skeletons: result.skeletons };
} catch (e: any) {
@ -112,9 +128,20 @@ export class AppModel extends Monobehiver {
}
}
/**
* 克隆模型材质,避免多个模型共享同名材质
* @param meshes 网格数组
* @param modelName 模型名称
*/
private cloneMaterials(meshes: AbstractMesh[], modelName: string): void {
meshes.forEach(mesh => {
if (mesh.material) {
const originalMaterial = mesh.material;
const clonedMaterial = originalMaterial.clone(`${originalMaterial.name}_${modelName}`);
mesh.material = clonedMaterial;
}
});
}
/** 为网格设置阴影(投射和接收) */
setupShadows(meshes: AbstractMesh[]): void {
const appLight = this.mainApp.appLight;
@ -129,64 +156,64 @@ export class AppModel extends Monobehiver {
}
/** 获取缓存的网格 */
getCachedMeshes(url: string): AbstractMesh | undefined {
return this.modelDic.Get(url);
getCachedMeshes(name: string): AbstractMesh[] | undefined {
return this.modelDic.Get(name);
}
/** 清理所有资源 */
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 模型名称(用于后续引用)
* @param modelUrl 模型URL路径
* 添加模型到场景(支持单个或批量)
* @param modelName 模型名称 或 模型配置数组
* @param modelUrl 模型URL(单个模型时使用)
*/
async add(modelName: string, modelUrl: string): Promise<LoadResult> {
// 检查是否已经存在该模型
const existingMesh = this.modelDic.Get(modelName);
if (existingMesh && !existingMesh.isDisposed()) {
console.log(`模型 ${modelName} 已存在,直接显示`);
existingMesh.setEnabled(true);
existingMesh.getChildMeshes().forEach(child => child.setEnabled(true));
return { success: true, meshes: [existingMesh, ...existingMesh.getChildMeshes()] };
async add(
modelName: string | ModelConfig[],
modelUrl?: string
): Promise<LoadResult | { success: boolean; results: LoadResult[] }> {
// 批量加载
if (Array.isArray(modelName)) {
return await this.addMultiple(modelName);
}
const handleProgress = (event: ISceneLoaderProgressEvent): void => {
const progress = event.lengthComputable && event.total > 0
? Math.min(1, event.loaded / event.total)
: 0;
EventBridge.modelLoadProgress({
loaded: progress,
total: 1,
url: modelUrl,
progress,
percentage: Number((progress * 100).toFixed(2)),
detail: {
url: modelUrl,
lengthComputable: event.lengthComputable,
loadedBytes: event.loaded,
totalBytes: event.total
}
});
};
// 单个加载
if (!modelUrl) {
return { success: false, error: '缺少模型URL参数' };
}
const result = await this.loadSingleModel(modelUrl, handleProgress);
return await this.addSingle(modelName, modelUrl);
}
/**
* 添加单个模型
*/
private async addSingle(modelName: string, modelUrl: string): Promise<LoadResult> {
// 检查是否已存在
const existingMeshes = this.modelDic.Get(modelName);
if (existingMeshes?.length && !existingMeshes[0].isDisposed()) {
console.log(`模型 ${modelName} 已存在,直接显示`);
this.showMeshes(existingMeshes);
return { success: true, meshes: existingMeshes };
}
// 加载模型
const result = await this.loadSingleModel(modelUrl, (event) => {
this.emitSingleProgress(modelUrl, event);
});
if (result.success && result.meshes) {
// 使用modelName作为key存储根网格
const rootMesh = result.meshes[0];
this.modelDic.Set(modelName, rootMesh);
this.cloneMaterials(result.meshes, modelName);
this.modelDic.Set(modelName, result.meshes);
// 更新 GameManager 的字典
this.mainApp.gameManager?.updateDictionaries();
EventBridge.modelLoaded({ urls: [modelUrl] });
} else {
EventBridge.modelLoadError({ url: modelUrl, error: result.error });
@ -196,33 +223,128 @@ export class AppModel extends Monobehiver {
}
/**
* 替换模型:销毁旧模型并加载新模型
* @param modelName 要替换的模型名称
* @param newModelUrl 新模型的URL路径
* 批量添加模型
*/
private async addMultiple(models: ModelConfig[]): Promise<{ success: boolean; results: LoadResult[] }> {
const total = models.length;
const results: LoadResult[] = [];
EventBridge.modelLoadProgress({ loaded: 0, total, progress: 0, percentage: 0 });
for (let i = 0; i < models.length; i++) {
const { name, url } = models[i];
const result = await this.loadSingleModel(url, (event) => {
this.emitProgress(i, total, url, event);
});
if (result.success && result.meshes) {
this.cloneMaterials(result.meshes, name);
this.modelDic.Set(name, result.meshes);
}
results.push(result);
this.emitProgress(i + 1, total, url, null, result.success);
}
// 批量加载完成后统一更新字典
this.mainApp.gameManager?.updateDictionaries();
EventBridge.modelLoaded({ urls: models.map(m => m.url) });
return {
success: results.every(r => r.success),
results
};
}
/**
* 显示网格
*/
private showMeshes(meshes: AbstractMesh[]): void {
meshes.forEach(mesh => {
mesh.setEnabled(true);
mesh.getChildMeshes().forEach(child => child.setEnabled(true));
});
}
/**
* 发送单个模型加载进度
*/
private emitSingleProgress(url: string, event: ISceneLoaderProgressEvent): void {
const progress = event.lengthComputable && event.total > 0
? Math.min(1, event.loaded / event.total)
: 0;
EventBridge.modelLoadProgress({
loaded: progress,
total: 1,
url,
progress,
percentage: Number((progress * 100).toFixed(2)),
detail: {
url,
lengthComputable: event.lengthComputable,
loadedBytes: event.loaded,
totalBytes: event.total
}
});
}
/**
* 根据 mesh 查找所属的模型名称
* @param mesh 网格对象
* @returns 模型名称,未找到返回 undefined
*/
findModelNameByMesh(mesh: AbstractMesh): string | undefined {
const keys = this.modelDic.Keys();
for (const key of keys) {
const meshes = this.modelDic.Get(key);
if (meshes?.some(m => m === mesh || m.uniqueId === mesh.uniqueId)) {
return key;
}
}
return undefined;
}
/**
* 根据 mesh 移除所属的整个模型
* @param mesh 网格对象
* @returns 是否成功移除
*/
remove(mesh: AbstractMesh): boolean {
const modelName = this.findModelNameByMesh(mesh);
if (modelName) {
this.removeByName(modelName);
return true;
}
console.warn('未找到该 mesh 所属的模型');
return false;
}
/**
* 替换模型
* @param modelName 模型名称
* @param newModelUrl 新模型URL
*/
async replaceModel(modelName: string, newModelUrl: string): Promise<LoadResult> {
// 先销毁旧模型
this.remove(modelName);
// 加载新模型
return await this.add(modelName, newModelUrl);
this.removeByName(modelName);
return await this.addSingle(modelName, newModelUrl);
}
/**
* 销毁指定模型
* @param modelName 模型名称
*/
remove(modelName: string): void {
const mesh = this.modelDic.Get(modelName);
if (mesh) {
// 隐藏网格而不是销毁,以便后续可以重新显示
mesh.setEnabled(false);
mesh.getChildMeshes().forEach(child => child.setEnabled(false));
console.log(`Model hidden: ${modelName}`);
} else {
removeByName(modelName: string): void {
const meshes = this.modelDic.Get(modelName);
if (!meshes?.length) {
console.warn(`Model not found: ${modelName}`);
return;
}
}
meshes.forEach(mesh => mesh.dispose());
this.modelDic.Remove(modelName);
console.log(`Model removed: ${modelName}`);
}
}