This commit is contained in:
2026-05-18 18:53:30 +08:00
parent fb193c0528
commit 840e3d6a55
12 changed files with 907 additions and 68 deletions

View File

@ -145,18 +145,24 @@ export class AppDropZone {
return null;
};
// 更新配置中的墙面分割数
this.dropZoneConfig.walls = this.dropZoneConfig.walls.map(wall => {
const newDivisions = matchWallName(wall.name);
const finalDivisions = newDivisions !== null ? newDivisions : (wall.divisions || 1);
// 更新配置中的墙面分割数,只保留后端配置的墙面
this.dropZoneConfig.walls = this.dropZoneConfig.walls
.map(wall => {
const newDivisions = matchWallName(wall.name);
console.log(`墙面 "${wall.name}" 匹配到分割数: ${finalDivisions}`);
// 如果后端没有配置这个墙面,返回 null 标记
if (newDivisions === null) {
return null;
}
return {
...wall,
divisions: finalDivisions
};
});
console.log(`墙面 "${wall.name}" 匹配到分割数: ${newDivisions}`);
return {
...wall,
divisions: newDivisions
};
})
.filter(wall => wall !== null) as typeof this.dropZoneConfig.walls; // 过滤掉未配置的墙面
// 清除旧的放置区域网格(不清除模型)
this.clearZones();

View File

@ -0,0 +1,748 @@
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';
import { AppConfig } from './AppConfig';
import { EventBridge } from '../event/bridge';
import { DragConfig } from './AppModelDrag';
type LoadResult = {
success: boolean;
meshes?: AbstractMesh[];
skeletons?: unknown[];
error?: string;
};
type ModelConfig = {
name: string;
url: string;
};
type ModelControlType = 'rotation' | 'color';
type ModelTransform = {
position?: { x: number; y: number; z: number };
rotation?: { x: number; y: number; z: number };
scale?: { x: number; y: number; z: number };
};
type ModelMetadata = {
modelName: string;
modelId: string;
modelUrl: string;
modelControlType?: ModelControlType;
drag?: DragConfig;
transform?: ModelTransform;
};
/**
* 模型管理类 - 负责加载、缓存和管理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;
}
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 modelId 模型ID
*/
private cloneMaterials(meshes: AbstractMesh[], modelId: string): void {
const scene = this.mainApp.appScene.object;
const clonedMaterials = new Map<string, any>();
meshes.forEach(mesh => {
if (mesh.material) {
const originalMaterial = mesh.material;
const originalName = originalMaterial.name;
// 如果该材质还没有被克隆过,则克隆它
if (!clonedMaterials.has(originalName)) {
const newName = `${originalName}_${modelId}`;
const clonedMaterial = originalMaterial.clone(newName);
clonedMaterials.set(originalName, clonedMaterial);
}
// 应用克隆的材质
mesh.material = clonedMaterials.get(originalName);
}
});
}
/** 为网格设置阴影(投射和接收) */
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;
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 modelConfig 模型配置对象 或 模型配置数组
*/
async add(
modelConfig: ModelMetadata | ModelMetadata[]
): Promise<LoadResult | { success: boolean; results: LoadResult[] }> {
// 批量加载
if (Array.isArray(modelConfig)) {
return await this.addMultiple(modelConfig);
}
// 单个加载
return await this.addSingle(
modelConfig.modelName,
modelConfig.modelId,
modelConfig.modelUrl,
modelConfig.modelControlType,
modelConfig.drag,
modelConfig.transform
);
}
/**
* 添加单个模型
*/
private async addSingle(modelName: string, modelId: string, modelUrl: string, modelControlType?: ModelControlType, drag?: DragConfig, transform?: ModelTransform): Promise<LoadResult> {
// 检查是否已存在
const existingMeshes = this.modelDic.Get(modelName+'_'+modelId);
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, modelId);
result.meshes = this.createModelRoot(modelName+'_'+modelId, result.meshes);
this.modelDic.Set(modelName+'_'+modelId, result.meshes);
// 存储元数据
this.modelMetadataDic.Set(modelName+'_'+modelId, {
modelName: modelName,
modelId: modelId,
modelUrl: modelUrl,
modelControlType: modelControlType,
drag: drag,
transform: transform
});
// 应用 transform
if (transform) {
this.applyTransform(modelName+'_'+modelId, transform);
}
// 配置拖拽功能
if (drag) {
this.mainApp.appModelDrag?.configureDrag(modelName+'_'+modelId, drag);
}
// 更新 GameManager 的字典
this.mainApp.gameManager?.updateDictionaries();
EventBridge.modelLoaded({ urls: [modelUrl] });
} else {
EventBridge.modelLoadError({ url: modelUrl, error: result.error });
}
return result;
}
/**
* 批量添加模型
*/
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 { modelName, modelId, modelUrl, modelControlType, drag, transform } = models[i];
const result = await this.loadSingleModel(modelUrl, (event) => {
this.emitProgress(i, total, modelUrl, event);
});
if (result.success && result.meshes) {
// 克隆材质,确保每个模型有独立的材质
this.cloneMaterials(result.meshes, modelId);
result.meshes = this.createModelRoot(modelId, result.meshes);
this.modelDic.Set(modelId, result.meshes);
// 存储元数据
this.modelMetadataDic.Set(modelId, {
modelName: modelName,
modelId: modelId,
modelUrl: modelUrl,
modelControlType: modelControlType,
drag: drag,
transform: transform
});
// 应用 transform
if (transform) {
this.applyTransform(modelId, transform);
}
// 配置拖拽功能
if (drag) {
this.mainApp.appModelDrag?.configureDrag(modelId, drag);
}
}
results.push(result);
this.emitProgress(i + 1, total, modelUrl, null, result.success);
}
// 批量加载完成后统一更新字典
this.mainApp.gameManager?.updateDictionaries();
EventBridge.modelLoaded({ urls: models.map(m => m.modelUrl) });
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);
meshes.forEach(mesh => {
console.log(mesh.uniqueId);
console.log(mesh.name);
});
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 modelConfig 模型配置对象
*/
async replaceModel(modelConfig: ModelMetadata): Promise<LoadResult> {
this.removeByName(modelConfig.modelId);
return await this.addSingle(
modelConfig.modelName,
modelConfig.modelId,
modelConfig.modelUrl,
modelConfig.modelControlType,
modelConfig.drag,
modelConfig.transform
);
}
/**
* 销毁指定模型
* @param modelName 模型名称
*/
removeByName(modelName: string): void {
const meshes = this.modelDic.Get(modelName);
if (!meshes?.length) {
console.warn(`Model not found: ${modelName}`);
return;
}
this.getModelTransformTargets(meshes).forEach(mesh => mesh.dispose(false, true));
this.modelDic.Remove(modelName);
this.modelMetadataDic.Remove(modelName);
this.mainApp.gameManager?.updateDictionaries();
}
/**
* 清除所有已添加的模型并释放内存
* 主要用于切换尺寸后清除不适用的配件
*/
removeAll(): void {
const modelNames = this.modelDic.Keys();
modelNames.forEach(modelName => {
const meshes = this.modelDic.Get(modelName);
if (meshes?.length) {
this.getModelTransformTargets(meshes).forEach(mesh => mesh.dispose(false, true));
}
});
this.modelDic.Clear();
this.modelMetadataDic.Clear();
this.mainApp.gameManager?.updateDictionaries();
console.log('所有模型已清除,内存已释放');
}
/**
* 获取模型元数据
* @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;
});
}
/**
* 将模型放置到指定的放置区域
* @param modelId 模型ID
* @param zoneInfo 放置区域信息
* @param offsetDistance 距离墙面的偏移距离默认0.1,正数向外)
*/
placeToZone(modelId: string, zoneInfo: any, offsetDistance: number = 0): void {
const meshes = this.modelDic.Get(modelId);
if (!meshes?.length) {
console.warn(`Model not found: ${modelId}`);
return;
}
// 计算放置位置:中心点 + 法线方向的偏移
const targetPosition = zoneInfo.center.add(zoneInfo.normal.scale(offsetDistance));
// 计算旋转角度:让模型面向墙面(法线的反方向)
const targetDirection = zoneInfo.normal.scale(-1);
const angle = Math.atan2(targetDirection.x, targetDirection.z);
this.getModelTransformTargets(meshes).forEach(mesh => {
// 设置位置
mesh.position.copyFrom(targetPosition);
// 设置旋转只旋转Y轴让模型面向墙面
if (mesh.rotationQuaternion) {
mesh.rotationQuaternion = Quaternion.FromEulerAngles(0, angle, 0);
} else {
mesh.rotation.set(0, angle, 0);
}
});
}
/**
* 检查模型是否存在
* @param modelId 模型ID
* @returns 模型是否存在
*/
exists(modelId: string): boolean {
return this.modelDic.Has(modelId);
}
/**
* 应用 transform 到模型
* @param modelId 模型ID
* @param transform 变换信息
*/
private applyTransform(modelId: string, transform: ModelTransform): void {
// 应用位置
if (transform.position) {
this.setPosition(modelId, transform.position);
}
// 应用旋转(角度制)
if (transform.rotation) {
this.setRotation(modelId, transform.rotation, true);
}
// 应用缩放
if (transform.scale) {
this.setScale(modelId, transform.scale);
}
}
}

View File

@ -31,6 +31,7 @@ type ModelTransform = {
};
type ModelMetadata = {
modelName: string;
modelId: string;
modelUrl: string;
modelControlType?: ModelControlType;
@ -253,6 +254,7 @@ export class AppModel extends Monobehiver {
// 单个加载
return await this.addSingle(
modelConfig.modelName,
modelConfig.modelId,
modelConfig.modelUrl,
modelConfig.modelControlType,
@ -264,9 +266,9 @@ export class AppModel extends Monobehiver {
/**
* 添加单个模型
*/
private async addSingle(modelName: string, modelUrl: string, modelControlType?: ModelControlType, drag?: DragConfig, transform?: ModelTransform): Promise<LoadResult> {
private async addSingle(modelName: string, modelId: string, modelUrl: string, modelControlType?: ModelControlType, drag?: DragConfig, transform?: ModelTransform): Promise<LoadResult> {
// 检查是否已存在
const existingMeshes = this.modelDic.Get(modelName);
const existingMeshes = this.modelDic.Get(modelId);
if (existingMeshes?.length && !existingMeshes[0].isDisposed()) {
console.log(`模型 ${modelName} 已存在,直接显示`);
this.showMeshes(existingMeshes);
@ -280,14 +282,15 @@ export class AppModel extends Monobehiver {
if (result.success && result.meshes) {
// 克隆材质,确保每个模型有独立的材质
this.cloneMaterials(result.meshes, modelName);
this.cloneMaterials(result.meshes, modelId);
result.meshes = this.createModelRoot(modelName, result.meshes);
this.modelDic.Set(modelName, result.meshes);
result.meshes = this.createModelRoot(modelId, result.meshes);
this.modelDic.Set(modelId, result.meshes);
// 存储元数据
this.modelMetadataDic.Set(modelName, {
modelId: modelName,
this.modelMetadataDic.Set(modelId, {
modelName: modelName,
modelId: modelId,
modelUrl: modelUrl,
modelControlType: modelControlType,
drag: drag,
@ -296,12 +299,12 @@ export class AppModel extends Monobehiver {
// 应用 transform
if (transform) {
this.applyTransform(modelName, transform);
this.applyTransform(modelId, transform);
}
// 配置拖拽功能
if (drag) {
this.mainApp.appModelDrag?.configureDrag(modelName, drag);
this.mainApp.appModelDrag?.configureDrag(modelId, drag);
}
// 更新 GameManager 的字典
@ -325,7 +328,7 @@ export class AppModel extends Monobehiver {
EventBridge.modelLoadProgress({ loaded: 0, total, progress: 0, percentage: 0 });
for (let i = 0; i < models.length; i++) {
const { modelId, modelUrl, modelControlType, drag, transform } = models[i];
const { modelName, modelId, modelUrl, modelControlType, drag, transform } = models[i];
const result = await this.loadSingleModel(modelUrl, (event) => {
this.emitProgress(i, total, modelUrl, event);
@ -340,6 +343,7 @@ export class AppModel extends Monobehiver {
// 存储元数据
this.modelMetadataDic.Set(modelId, {
modelName: modelName,
modelId: modelId,
modelUrl: modelUrl,
modelControlType: modelControlType,
@ -483,6 +487,7 @@ export class AppModel extends Monobehiver {
this.removeByName(modelConfig.modelId);
return await this.addSingle(
modelConfig.modelName,
modelConfig.modelId,
modelConfig.modelUrl,
modelConfig.modelControlType,

View File

@ -143,9 +143,13 @@ class AppRay extends Monobehiver {
// 获取模型元数据
const modelMetadata = this.mainApp.appModel.getMetadataByMesh(pickInfo.pickedMesh);
// 获取模型名称(优先使用 modelName如果没有则使用 modelId
const modelName = this.mainApp.appModel.findModelNameByMesh(pickInfo.pickedMesh);
console.log(modelName);
EventBridge.modelClick({
meshName: pickInfo.pickedMesh.name,
modelName: modelName,
pickedMesh: pickInfo.pickedMesh,
pickedPoint: pickInfo.pickedPoint,
materialName: materialName,

View File

@ -786,18 +786,33 @@ export class GameManager extends Monobehiver {
}
// 应用反照率纹理(颜色贴图)
if (options.albedoTexture) {
material.albedoTexture = new Texture(options.albedoTexture);
if (options.albedoTexture !== undefined) {
if (options.albedoTexture) {
material.albedoTexture = new Texture(options.albedoTexture);
} else {
// 传入空字符串或 null 时清空贴图
material.albedoTexture = null;
}
}
// 应用法线贴图
if (options.normalMap) {
material.bumpTexture = new Texture(options.normalMap);
if (options.normalMap !== undefined) {
if (options.normalMap) {
material.bumpTexture = new Texture(options.normalMap);
} else {
// 传入空字符串或 null 时清空贴图
material.bumpTexture = null;
}
}
// 应用金属度贴图
if (options.metallicTexture) {
material.metallicTexture = new Texture(options.metallicTexture);
if (options.metallicTexture !== undefined) {
if (options.metallicTexture) {
material.metallicTexture = new Texture(options.metallicTexture);
} else {
// 传入空字符串或 null 时清空贴图
material.metallicTexture = null;
}
}
// 应用粗糙度值

View File

@ -30,6 +30,7 @@ export type ModelLoadedPayload = {
export type ModelClickPayload = {
meshName?: string;
modelName?: string; // 模型根节点名称modelId
pickedMesh?: any;
pickedPoint?: any;
materialName?: string;

View File

@ -5,6 +5,7 @@ import type { HotspotInput } from '../types/hotspot';
type ModelControlType = 'rotation' | 'color';
type ModelInput = {
modelName: string;
modelId: string;
modelUrl: string;
modelControlType?: ModelControlType;
@ -68,6 +69,19 @@ export class KernelAdapter {
*/
removeAll: (): void => {
this.mainApp.appModel.removeAll();
},
/**
* 检查模型是否已加载
* @param modelId 模型ID
* @returns 模型是否存在
* @example
* // 检查模型是否已加载,避免重复加载
* if (!kernel.model.exists('shed_001')) {
* await kernel.model.add({ modelId: 'shed_001', modelUrl: '...' });
* }
*/
exists: (modelId: string): boolean => {
return this.mainApp.appModel.exists(modelId);
}
};