1
This commit is contained in:
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user