Files
zhengte.babylonjs-sdk/src/babylonjs/AppModel.ts
2026-04-24 19:39:58 +08:00

381 lines
12 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 { 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 { 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;
};
type ModelConfig = {
name: string;
url: string;
};
/**
* 模型管理类 - 负责加载、缓存和管理3D模型
*/
export class AppModel extends Monobehiver {
private modelDic: Dictionary<AbstractMesh[]>;
private loadedMeshes: AbstractMesh[];
private isLoading: boolean;
constructor(mainApp: any) {
super(mainApp);
this.modelDic = new Dictionary<AbstractMesh[]>();
this.loadedMeshes = [];
this.isLoading = false;
}
initManagers(): void {
// 预留接口
}
/** 加载配置中的所有模型 */
async loadModel(): Promise<void> {
if (!AppConfig.modelUrlList?.length || this.isLoading) return;
this.isLoading = true;
try {
await this.loadMultipleModels(AppConfig.modelUrlList);
EventBridge.modelLoaded({ urls: AppConfig.modelUrlList });
} finally {
this.isLoading = false;
}
}
/**
* 批量加载模型(内部方法)
* @param urls 模型URL数组
*/
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 = 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.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 };
}
}
/**
* 克隆模型材质,避免多个模型共享同名材质
* @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;
if (!appLight) return;
meshes.forEach(mesh => {
if (mesh.getTotalVertices() > 0) {
appLight.addShadowCaster(mesh);
mesh.receiveShadows = true;
}
});
}
/** 获取缓存的网格 */
getCachedMeshes(name: string): AbstractMesh[] | undefined {
return this.modelDic.Get(name);
}
/** 清理所有资源 */
clean(): void {
this.modelDic.Clear();
this.loadedMeshes.forEach(m => m?.dispose());
this.loadedMeshes = [];
this.isLoading = false;
}
/**
* 添加模型到场景(支持单个或批量)
* @param modelName 模型名称 或 模型配置数组
* @param modelUrl 模型URL单个模型时使用
*/
async add(
modelName: string | ModelConfig[],
modelUrl?: string
): Promise<LoadResult | { success: boolean; results: LoadResult[] }> {
// 批量加载
if (Array.isArray(modelName)) {
return await this.addMultiple(modelName);
}
// 单个加载
if (!modelUrl) {
return { success: false, error: '缺少模型URL参数' };
}
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) {
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 });
}
return result;
}
/**
* 批量添加模型
*/
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 名称查找 mesh 对象
* @param meshName mesh 名称
* @returns mesh 对象,未找到返回 undefined
*/
private findMeshByName(meshName: string): AbstractMesh | undefined {
const keys = this.modelDic.Keys();
for (const key of keys) {
const meshes = this.modelDic.Get(key);
const found = meshes?.find(m => m.name === meshName);
if (found) return found;
}
return undefined;
}
/**
* 根据 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 或 mesh 名称移除所属的整个模型
* @param meshOrName 网格对象或网格名称
* @returns 是否成功移除
*/
remove(meshOrName: AbstractMesh | string): boolean {
let mesh: AbstractMesh | undefined;
// 判断传入的是对象还是字符串
if (typeof meshOrName === 'string') {
mesh = this.findMeshByName(meshOrName);
if (!mesh) {
console.warn(`未找到名为 ${meshOrName} 的网格`);
return false;
}
} else {
mesh = meshOrName;
}
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> {
console.log( modelName,this.modelDic);
this.removeByName(modelName);
return await this.addSingle(modelName, newModelUrl);
}
/**
* 销毁指定模型
* @param modelName 模型名称
*/
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}`);
}
}