This commit is contained in:
2026-04-30 14:46:01 +08:00
parent 604dcdf3fb
commit 4207fcf7c2
15 changed files with 1038 additions and 87 deletions

View File

@ -1,6 +1,8 @@
import { ImportMeshAsync, ISceneLoaderProgressEvent } from '@babylonjs/core/Loading/sceneLoader';
import '@babylonjs/loaders/glTF';
import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
import { Mesh } from '@babylonjs/core/Meshes/mesh';
import { Quaternion, Vector3 } from '@babylonjs/core/Maths/math.vector';
import { Scene } from '@babylonjs/core/scene';
import { Monobehiver } from '../base/Monobehiver';
import { Dictionary } from '../utils/Dictionary';
@ -19,17 +21,27 @@ type ModelConfig = {
url: string;
};
type ModelControlType = 'rotation' | 'color';
type ModelMetadata = {
modelId: string;
modelUrl: string;
modelControlType?: ModelControlType;
};
/**
* 模型管理类 - 负责加载、缓存和管理3D模型
*/
export class AppModel extends Monobehiver {
private modelDic: Dictionary<AbstractMesh[]>;
private modelMetadataDic: Dictionary<ModelMetadata>;
private loadedMeshes: AbstractMesh[];
private isLoading: boolean;
constructor(mainApp: any) {
super(mainApp);
this.modelDic = new Dictionary<AbstractMesh[]>();
this.modelMetadataDic = new Dictionary<ModelMetadata>();
this.loadedMeshes = [];
this.isLoading = false;
}
@ -143,6 +155,40 @@ export class AppModel extends Monobehiver {
});
}
/** 为网格设置阴影(投射和接收) */
private createModelRoot(modelId: string, meshes: AbstractMesh[]): AbstractMesh[] {
const scene = this.mainApp.appScene.object;
const root = new Mesh(`${modelId}__root`, scene);
const meshSet = new Set<AbstractMesh>(meshes);
root.position.copyFrom(this.getMeshesBoundingCenter(meshes));
meshes.forEach(mesh => {
if (!mesh.parent || !meshSet.has(mesh.parent as AbstractMesh)) {
mesh.setParent(root, true, true);
}
});
this.loadedMeshes.push(root);
return [root, ...meshes];
}
private getMeshesBoundingCenter(meshes: AbstractMesh[]): Vector3 {
const renderableMeshes = meshes.filter(mesh => !mesh.isDisposed() && mesh.getTotalVertices() > 0);
if (!renderableMeshes.length) return Vector3.Zero();
const min = new Vector3(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY);
const max = new Vector3(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY);
renderableMeshes.forEach(mesh => {
mesh.computeWorldMatrix(true);
const boundingBox = mesh.getBoundingInfo().boundingBox;
min.minimizeInPlace(boundingBox.minimumWorld);
max.maximizeInPlace(boundingBox.maximumWorld);
});
return min.add(max).scaleInPlace(0.5);
}
setupShadows(meshes: AbstractMesh[]): void {
const appLight = this.mainApp.appLight;
if (!appLight) return;
@ -170,30 +216,28 @@ export class AppModel extends Monobehiver {
/**
* 添加模型到场景(支持单个或批量)
* @param modelName 模型名称 或 模型配置数组
* @param modelUrl 模型URL单个模型时使用
* @param modelConfig 模型配置对象 或 模型配置数组
*/
async add(
modelName: string | ModelConfig[],
modelUrl?: string
modelConfig: ModelMetadata | ModelMetadata[]
): Promise<LoadResult | { success: boolean; results: LoadResult[] }> {
// 批量加载
if (Array.isArray(modelName)) {
return await this.addMultiple(modelName);
if (Array.isArray(modelConfig)) {
return await this.addMultiple(modelConfig);
}
// 单个加载
if (!modelUrl) {
return { success: false, error: '缺少模型URL参数' };
}
return await this.addSingle(modelName, modelUrl);
return await this.addSingle(
modelConfig.modelId,
modelConfig.modelUrl,
modelConfig.modelControlType
);
}
/**
* 添加单个模型
*/
private async addSingle(modelName: string, modelUrl: string): Promise<LoadResult> {
private async addSingle(modelName: string, modelUrl: string, modelControlType?: ModelControlType): Promise<LoadResult> {
// 检查是否已存在
const existingMeshes = this.modelDic.Get(modelName);
if (existingMeshes?.length && !existingMeshes[0].isDisposed()) {
@ -209,8 +253,16 @@ export class AppModel extends Monobehiver {
if (result.success && result.meshes) {
// this.cloneMaterials(result.meshes, modelName);
result.meshes = this.createModelRoot(modelName, result.meshes);
this.modelDic.Set(modelName, result.meshes);
// 存储元数据
this.modelMetadataDic.Set(modelName, {
modelId: modelName,
modelUrl: modelUrl,
modelControlType: modelControlType
});
// 更新 GameManager 的字典
this.mainApp.gameManager?.updateDictionaries();
@ -225,32 +277,40 @@ export class AppModel extends Monobehiver {
/**
* 批量添加模型
*/
private async addMultiple(models: ModelConfig[]): Promise<{ success: boolean; results: LoadResult[] }> {
private async addMultiple(models: ModelMetadata[]): 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 { modelId, modelUrl, modelControlType } = models[i];
const result = await this.loadSingleModel(url, (event) => {
this.emitProgress(i, total, url, event);
const result = await this.loadSingleModel(modelUrl, (event) => {
this.emitProgress(i, total, modelUrl, event);
});
if (result.success && result.meshes) {
this.cloneMaterials(result.meshes, name);
this.modelDic.Set(name, result.meshes);
result.meshes = this.createModelRoot(modelId, result.meshes);
this.cloneMaterials(result.meshes, modelId);
this.modelDic.Set(modelId, result.meshes);
// 存储元数据
this.modelMetadataDic.Set(modelId, {
modelId: modelId,
modelUrl: modelUrl,
modelControlType: modelControlType
});
}
results.push(result);
this.emitProgress(i + 1, total, url, null, result.success);
this.emitProgress(i + 1, total, modelUrl, null, result.success);
}
// 批量加载完成后统一更新字典
this.mainApp.gameManager?.updateDictionaries();
EventBridge.modelLoaded({ urls: models.map(m => m.url) });
EventBridge.modelLoaded({ urls: models.map(m => m.modelUrl) });
return {
success: results.every(r => r.success),
@ -352,14 +412,17 @@ export class AppModel extends Monobehiver {
/**
* 替换模型
* @param modelName 模型名称
* @param newModelUrl 新模型URL
* @param modelConfig 模型配置对象
*/
async replaceModel(modelName: string, newModelUrl: string): Promise<LoadResult> {
console.log( modelName,this.modelDic);
this.removeByName(modelName);
return await this.addSingle(modelName, newModelUrl);
async replaceModel(modelConfig: ModelMetadata): Promise<LoadResult> {
console.log(modelConfig.modelId, this.modelDic);
this.removeByName(modelConfig.modelId);
return await this.addSingle(
modelConfig.modelId,
modelConfig.modelUrl,
modelConfig.modelControlType
);
}
/**
@ -375,6 +438,151 @@ export class AppModel extends Monobehiver {
meshes.forEach(mesh => mesh.dispose());
this.modelDic.Remove(modelName);
this.modelMetadataDic.Remove(modelName);
console.log(`Model removed: ${modelName}`);
}
/**
* 获取模型元数据
* @param modelName 模型名称
*/
getModelMetadata(modelName: string): ModelMetadata | undefined {
return this.modelMetadataDic.Get(modelName);
}
/**
* 根据网格查找模型元数据
* @param mesh 网格对象
*/
getMetadataByMesh(mesh: AbstractMesh): ModelMetadata | undefined {
const modelName = this.findModelNameByMesh(mesh);
if (modelName) {
return this.modelMetadataDic.Get(modelName);
}
return undefined;
}
private getModelTransformTargets(meshes: AbstractMesh[]): AbstractMesh[] {
const meshSet = new Set<AbstractMesh>(meshes);
const rootMeshes = meshes.filter(mesh => !mesh.parent || !meshSet.has(mesh.parent as AbstractMesh));
return rootMeshes.length ? rootMeshes : meshes.slice(0, 1);
}
getModelTransformTargetByMesh(mesh: AbstractMesh): AbstractMesh | undefined {
const modelName = this.findModelNameByMesh(mesh);
if (!modelName) return mesh;
const meshes = this.modelDic.Get(modelName);
if (!meshes?.length) return mesh;
return this.getModelTransformTargets(meshes)[0] ?? mesh;
}
getModelMeshesByMesh(mesh: AbstractMesh): AbstractMesh[] {
const modelName = this.findModelNameByMesh(mesh);
if (!modelName) return [mesh];
const meshes = this.modelDic.Get(modelName);
return meshes?.length ? meshes : [mesh];
}
/**
* 设置模型旋转
* @param modelId 模型ID
* @param rotation 旋转向量 {x, y, z}(默认使用角度)
* @param useDegrees 是否使用角度默认true
*/
setRotation(modelId: string, rotation: { x: number; y: number; z: number }, useDegrees: boolean = true): void {
const meshes = this.modelDic.Get(modelId);
if (!meshes?.length) {
console.warn(`Model not found: ${modelId}`);
return;
}
// 如果使用角度,转换为弧度
const toRadians = (degrees: number) => degrees * Math.PI / 180;
const rotationValues = useDegrees ? {
x: toRadians(rotation.x),
y: toRadians(rotation.y),
z: toRadians(rotation.z)
} : rotation;
this.getModelTransformTargets(meshes).forEach(mesh => {
if (mesh.rotationQuaternion) {
mesh.rotationQuaternion = Quaternion.FromEulerAngles(
rotationValues.x,
rotationValues.y,
rotationValues.z
);
return;
}
mesh.rotation.set(rotationValues.x, rotationValues.y, rotationValues.z);
});
}
/**
* 累加模型旋转
* @param modelId 模型ID
* @param rotation 旋转向量 {x, y, z}(默认使用角度)
* @param useDegrees 是否使用角度默认true
*/
addRotation(modelId: string, rotation: { x: number; y: number; z: number }, useDegrees: boolean = true): void {
const meshes = this.modelDic.Get(modelId);
if (!meshes?.length) {
console.warn(`Model not found: ${modelId}`);
return;
}
// 如果使用角度,转换为弧度
const toRadians = (degrees: number) => degrees * Math.PI / 180;
const rotationValues = useDegrees ? {
x: toRadians(rotation.x),
y: toRadians(rotation.y),
z: toRadians(rotation.z)
} : rotation;
this.getModelTransformTargets(meshes).forEach(mesh => {
mesh.addRotation(rotationValues.x, rotationValues.y, rotationValues.z);
});
}
/**
* 设置模型位置
* @param modelId 模型ID
* @param position 位置向量 {x, y, z}
*/
setPosition(modelId: string, position: { x: number; y: number; z: number }): void {
const meshes = this.modelDic.Get(modelId);
if (!meshes?.length) {
console.warn(`Model not found: ${modelId}`);
return;
}
this.getModelTransformTargets(meshes).forEach(mesh => {
mesh.position.x = position.x;
mesh.position.y = position.y;
mesh.position.z = position.z;
});
}
/**
* 设置模型缩放
* @param modelId 模型ID
* @param scale 缩放向量 {x, y, z}
*/
setScale(modelId: string, scale: { x: number; y: number; z: number }): void {
const meshes = this.modelDic.Get(modelId);
if (!meshes?.length) {
console.warn(`Model not found: ${modelId}`);
return;
}
this.getModelTransformTargets(meshes).forEach(mesh => {
mesh.scaling.x = scale.x;
mesh.scaling.y = scale.y;
mesh.scaling.z = scale.z;
});
}
}