This commit is contained in:
2026-05-13 10:43:06 +08:00
parent 6cefd063f2
commit 223fa5dd4e
19 changed files with 2282 additions and 104 deletions

View File

@ -32,7 +32,7 @@ export class AppCamera extends Monobehiver {
// this.object.upperBetaLimit = Tools.ToRadians(60); // 最大垂直角接近90度避免万向锁
// this.object.lowerBetaLimit = Tools.ToRadians(60); // 最小垂直角
this.object.position = new Vector3(-0, 100, 0);
this.object.position = new Vector3(-0, 10, 0);
this.setTarget(0, 2, 0);
}

View File

@ -0,0 +1,540 @@
import { Scene, Mesh, MeshBuilder, StandardMaterial, Color3, Vector3, AbstractMesh, BoundingBoxGizmo } from '@babylonjs/core';
export interface DropZoneConfig {
modelName: string; // 目标模型名称
divisions: number; // 分割块数(每条边分成几块)
color?: string; // 颜色(十六进制)
alpha?: number; // 透明度
thickness?: number; // 厚度
offset?: number; // 距离模型的偏移量
scale?: number; // 整体缩放比例0-1用于生成内部放置区域
}
export class AppDropZone {
private scene: Scene;
private dropZones: Mesh[] = [];
private dropZoneConfigs: Map<string, any[]> = new Map(); // 存储每个模型的放置区域配置
private boundingBoxLines: Mesh[] = []; // 存储包围盒线框
constructor(scene: Scene) {
this.scene = scene;
}
/**
* 根据模型包围盒生成四周的放置区域
* @param config 配置参数
*/
generateDropZones(config: DropZoneConfig): Mesh[] {
const {
modelName,
divisions,
color = '#21c7ff',
alpha = 0.3,
thickness = 2,
offset = 5,
scale = 1.0
} = config;
// 查找目标模型(支持 modelId 或 mesh name
let targetMeshes: AbstractMesh[] | undefined;
// 先尝试通过 modelId 查找(从 AppModel 的 modelDic
const mainApp = (this.scene as any).mainApp;
if (mainApp?.appModel) {
targetMeshes = mainApp.appModel.getCachedMeshes(modelName);
}
// 如果没找到,尝试通过 mesh name 查找
if (!targetMeshes || targetMeshes.length === 0) {
const mesh = this.scene.getMeshByName(modelName);
if (mesh) {
targetMeshes = [mesh];
}
}
if (!targetMeshes || targetMeshes.length === 0) {
console.warn(`模型 ${modelName} 不存在`);
return [];
}
// 计算所有网格的总包围盒(使用世界坐标)
let minX = Infinity, minY = Infinity, minZ = Infinity;
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
targetMeshes.forEach(mesh => {
// 强制更新世界矩阵
mesh.computeWorldMatrix(true);
const boundingInfo = mesh.getBoundingInfo();
// 获取世界空间的包围盒
const worldMin = boundingInfo.boundingBox.minimumWorld;
const worldMax = boundingInfo.boundingBox.maximumWorld;
minX = Math.min(minX, worldMin.x);
minY = Math.min(minY, worldMin.y);
minZ = Math.min(minZ, worldMin.z);
maxX = Math.max(maxX, worldMax.x);
maxY = Math.max(maxY, worldMax.y);
maxZ = Math.max(maxZ, worldMax.z);
});
console.log('包围盒坐标:', { minX, minY, minZ, maxX, maxY, maxZ });
console.log('包围盒尺寸:', {
width: maxX - minX,
height: maxY - minY,
depth: maxZ - minZ
});
const width = maxX - minX;
const height = maxY - minY;
const depth = maxZ - minZ;
// 应用缩放比例
const centerX = (minX + maxX) / 2;
const centerY = (minY + maxY) / 2;
const centerZ = (minZ + maxZ) / 2;
const scaledWidth = width * scale;
const scaledHeight = height * scale;
const scaledDepth = depth * scale;
const scaledMinX = centerX - scaledWidth / 2;
const scaledMaxX = centerX + scaledWidth / 2;
const scaledMinY = centerY - scaledHeight / 2;
const scaledMaxY = centerY + scaledHeight / 2;
const scaledMinZ = centerZ - scaledDepth / 2;
const scaledMaxZ = centerZ + scaledDepth / 2;
// 计算每块的尺寸
const blockWidth = scaledWidth / divisions;
const blockDepth = scaledDepth / divisions;
const zones: Mesh[] = [];
const zoneConfigs: any[] = [];
// 创建材质
const material = this.createDropZoneMaterial(color, alpha);
// 前面Z轴负方向
for (let i = 0; i < divisions; i++) {
const x = scaledMinX + blockWidth * i + blockWidth / 2;
const z = scaledMinZ - offset;
const position = new Vector3(x, scaledMinY, z);
const zone = this.createDropZonePlane(
`dropZone_${modelName}_front_${i}`,
blockWidth,
scaledHeight,
position,
0,
material,
thickness
);
zones.push(zone);
zoneConfigs.push({
position: position.clone(),
width: blockWidth,
height: scaledHeight,
rotation: 0,
side: 'front',
index: i
});
}
// 后面Z轴正方向
for (let i = 0; i < divisions; i++) {
const x = scaledMinX + blockWidth * i + blockWidth / 2;
const z = scaledMaxZ + offset;
const position = new Vector3(x, scaledMinY, z);
const zone = this.createDropZonePlane(
`dropZone_${modelName}_back_${i}`,
blockWidth,
scaledHeight,
position,
0,
material,
thickness
);
zones.push(zone);
zoneConfigs.push({
position: position.clone(),
width: blockWidth,
height: scaledHeight,
rotation: 0,
side: 'back',
index: i
});
}
// 左侧X轴负方向
for (let i = 0; i < divisions; i++) {
const x = scaledMinX - offset;
const z = scaledMinZ + blockDepth * i + blockDepth / 2;
const position = new Vector3(x, scaledMinY, z);
const zone = this.createDropZonePlane(
`dropZone_${modelName}_left_${i}`,
blockDepth,
scaledHeight,
position,
Math.PI / 2,
material,
thickness
);
zones.push(zone);
zoneConfigs.push({
position: position.clone(),
width: blockDepth,
height: scaledHeight,
rotation: Math.PI / 2,
side: 'left',
index: i
});
}
// 右侧X轴正方向
for (let i = 0; i < divisions; i++) {
const x = scaledMaxX + offset;
const z = scaledMinZ + blockDepth * i + blockDepth / 2;
const position = new Vector3(x, scaledMinY, z);
const zone = this.createDropZonePlane(
`dropZone_${modelName}_right_${i}`,
blockDepth,
scaledHeight,
position,
Math.PI / 2,
material,
thickness
);
zones.push(zone);
zoneConfigs.push({
position: position.clone(),
width: blockDepth,
height: scaledHeight,
rotation: Math.PI / 2,
side: 'right',
index: i
});
}
// 保存配置
this.dropZoneConfigs.set(modelName, zoneConfigs);
this.dropZones.push(...zones);
// 显示包围盒
this.showBoundingBox(modelName, '#ff0000');
// 默认隐藏
zones.forEach(zone => zone.setEnabled(false));
return zones;
}
/**
* 创建单个放置区域平面
*/
private createDropZonePlane(
name: string,
width: number,
height: number,
position: Vector3,
rotationY: number,
material: StandardMaterial,
thickness: number
): Mesh {
// 创建主平面
const plane = MeshBuilder.CreatePlane(name, {
width: width,
height: height
}, this.scene);
plane.position = position;
plane.rotation.y = rotationY;
plane.material = material;
plane.isPickable = true; // 可以被拾取,用于检测拖拽
plane.metadata = { isDropZone: true }; // 标记为放置区域
// 创建边框线
this.createBorder(plane, width, height, thickness);
return plane;
}
/**
* 创建边框线
*/
private createBorder(parent: Mesh, width: number, height: number, thickness: number): void {
const halfWidth = width / 2;
const halfHeight = height / 2;
const points = [
new Vector3(-halfWidth, -halfHeight, -0.01),
new Vector3(halfWidth, -halfHeight, -0.01),
new Vector3(halfWidth, halfHeight, -0.01),
new Vector3(-halfWidth, halfHeight, -0.01),
new Vector3(-halfWidth, -halfHeight, -0.01)
];
const border = MeshBuilder.CreateLines(`${parent.name}_border`, {
points: points
}, this.scene);
border.color = new Color3(1, 1, 1); // 白色边框
border.parent = parent;
}
/**
* 创建放置区域材质
*/
private createDropZoneMaterial(hexColor: string, alpha: number): StandardMaterial {
const material = new StandardMaterial('dropZoneMat_' + Date.now(), this.scene);
const rgb = this.hexToRgb(hexColor);
material.diffuseColor = new Color3(rgb.r, rgb.g, rgb.b);
material.alpha = alpha;
material.backFaceCulling = false; // 双面显示
return material;
}
/**
* 十六进制颜色转 RGB
*/
private hexToRgb(hex: string): { r: number; g: number; b: number } {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16) / 255,
g: parseInt(result[2], 16) / 255,
b: parseInt(result[3], 16) / 255
} : { r: 0.13, g: 0.78, b: 1 };
}
/**
* 显示所有放置区域
*/
showAllDropZones(): void {
this.dropZones.forEach(zone => zone.setEnabled(true));
// 显示包围盒
this.boundingBoxLines.forEach(line => line.setEnabled(true));
}
/**
* 隐藏所有放置区域
*/
hideAllDropZones(): void {
this.dropZones.forEach(zone => zone.setEnabled(false));
// 隐藏包围盒
this.boundingBoxLines.forEach(line => line.setEnabled(false));
}
/**
* 显示指定模型的放置区域
*/
showDropZonesForModel(modelName: string): void {
this.dropZones
.filter(zone => zone.name.includes(`dropZone_${modelName}_`))
.forEach(zone => zone.setEnabled(true));
}
/**
* 隐藏指定模型的放置区域
*/
hideDropZonesForModel(modelName: string): void {
this.dropZones
.filter(zone => zone.name.includes(`dropZone_${modelName}_`))
.forEach(zone => zone.setEnabled(false));
}
/**
* 检查某个位置是否在放置区域内
* @param position 要检查的位置
* @returns 如果在放置区域内,返回该区域的配置信息,否则返回 null
*/
checkInDropZone(position: Vector3): { zone: Mesh; config: any } | null {
for (const zone of this.dropZones) {
if (!zone.isEnabled()) continue;
// 简单的距离检测
const distance = Vector3.Distance(
new Vector3(position.x, 0, position.z),
new Vector3(zone.position.x, 0, zone.position.z)
);
// 获取区域的宽度(从 scaling 或原始尺寸计算)
const zoneBounds = zone.getBoundingInfo();
const zoneSize = zoneBounds.boundingBox.extendSize;
const maxDistance = Math.max(zoneSize.x, zoneSize.z);
if (distance < maxDistance) {
// 找到对应的配置
const modelName = zone.name.split('_')[1];
const configs = this.dropZoneConfigs.get(modelName);
const configIndex = parseInt(zone.name.split('_').pop() || '0');
const config = configs ? configs.find(c => c.index === configIndex) : null;
return { zone, config };
}
}
return null;
}
/**
* 高亮某个放置区域(鼠标悬停效果)
*/
highlightDropZone(zone: Mesh): void {
const material = zone.material as StandardMaterial;
if (material) {
material.alpha = 0.6; // 增加透明度
material.emissiveColor = new Color3(0.2, 0.2, 0.2); // 添加发光效果
}
}
/**
* 取消高亮
*/
unhighlightDropZone(zone: Mesh): void {
const material = zone.material as StandardMaterial;
if (material) {
material.alpha = 0.3; // 恢复透明度
material.emissiveColor = new Color3(0, 0, 0); // 移除发光效果
}
}
/**
* 清除所有放置区域
*/
clearAllDropZones(): void {
this.dropZones.forEach(zone => {
zone.dispose();
});
this.dropZones = [];
this.dropZoneConfigs.clear();
}
/**
* 清除指定模型的放置区域
*/
clearDropZonesForModel(modelName: string): void {
const zonesToRemove = this.dropZones.filter(zone =>
zone.name.includes(`dropZone_${modelName}_`)
);
zonesToRemove.forEach(zone => {
zone.dispose();
const index = this.dropZones.indexOf(zone);
if (index > -1) {
this.dropZones.splice(index, 1);
}
});
this.dropZoneConfigs.delete(modelName);
}
/**
* 获取所有放置区域
*/
getAllDropZones(): Mesh[] {
return this.dropZones;
}
/**
* 获取指定模型的放置区域配置
*/
getDropZoneConfigsForModel(modelName: string): any[] {
return this.dropZoneConfigs.get(modelName) || [];
}
/**
* 显示模型的包围盒
* @param modelName 模型名称
* @param color 包围盒颜色
*/
private showBoundingBox(modelName: string, color: string = '#ff0000'): void {
// 查找目标模型
let targetMeshes: AbstractMesh[] | undefined;
const mainApp = (this.scene as any).mainApp;
if (mainApp?.appModel) {
targetMeshes = mainApp.appModel.getCachedMeshes(modelName);
}
if (!targetMeshes || targetMeshes.length === 0) {
const mesh = this.scene.getMeshByName(modelName);
if (mesh) {
targetMeshes = [mesh];
}
}
if (!targetMeshes || targetMeshes.length === 0) {
console.warn(`模型 ${modelName} 不存在`);
return;
}
// 计算总包围盒(使用世界坐标)
let minX = Infinity, minY = Infinity, minZ = Infinity;
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
targetMeshes.forEach(mesh => {
// 强制更新世界矩阵
mesh.computeWorldMatrix(true);
const boundingInfo = mesh.getBoundingInfo();
// 获取世界空间的包围盒
const worldMin = boundingInfo.boundingBox.minimumWorld;
const worldMax = boundingInfo.boundingBox.maximumWorld;
minX = Math.min(minX, worldMin.x);
minY = Math.min(minY, worldMin.y);
minZ = Math.min(minZ, worldMin.z);
maxX = Math.max(maxX, worldMax.x);
maxY = Math.max(maxY, worldMax.y);
maxZ = Math.max(maxZ, worldMax.z);
});
// 创建包围盒的8个顶点
const corners = [
new Vector3(minX, minY, minZ),
new Vector3(maxX, minY, minZ),
new Vector3(maxX, minY, maxZ),
new Vector3(minX, minY, maxZ),
new Vector3(minX, maxY, minZ),
new Vector3(maxX, maxY, minZ),
new Vector3(maxX, maxY, maxZ),
new Vector3(minX, maxY, maxZ)
];
// 创建12条边
const edges = [
// 底面4条边
[corners[0], corners[1]],
[corners[1], corners[2]],
[corners[2], corners[3]],
[corners[3], corners[0]],
// 顶面4条边
[corners[4], corners[5]],
[corners[5], corners[6]],
[corners[6], corners[7]],
[corners[7], corners[4]],
// 4条竖边
[corners[0], corners[4]],
[corners[1], corners[5]],
[corners[2], corners[6]],
[corners[3], corners[7]]
];
const rgb = this.hexToRgb(color);
const lineColor = new Color3(rgb.r, rgb.g, rgb.b);
edges.forEach((edge, index) => {
const line = MeshBuilder.CreateLines(`boundingBox_${modelName}_${index}`, {
points: edge
}, this.scene);
line.color = lineColor;
this.boundingBoxLines.push(line);
});
}
/**
* 隐藏所有包围盒
*/
private hideBoundingBox(): void {
this.boundingBoxLines.forEach(line => line.dispose());
this.boundingBoxLines = [];
}
}

View File

@ -8,6 +8,7 @@ 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;
@ -27,6 +28,7 @@ type ModelMetadata = {
modelId: string;
modelUrl: string;
modelControlType?: ModelControlType;
drag?: DragConfig;
};
/**
@ -221,14 +223,15 @@ export class AppModel extends Monobehiver {
return await this.addSingle(
modelConfig.modelId,
modelConfig.modelUrl,
modelConfig.modelControlType
modelConfig.modelControlType,
modelConfig.drag
);
}
/**
* 添加单个模型
*/
private async addSingle(modelName: string, modelUrl: string, modelControlType?: ModelControlType): Promise<LoadResult> {
private async addSingle(modelName: string, modelUrl: string, modelControlType?: ModelControlType, drag?: DragConfig): Promise<LoadResult> {
// 检查是否已存在
const existingMeshes = this.modelDic.Get(modelName);
if (existingMeshes?.length && !existingMeshes[0].isDisposed()) {
@ -250,9 +253,15 @@ export class AppModel extends Monobehiver {
this.modelMetadataDic.Set(modelName, {
modelId: modelName,
modelUrl: modelUrl,
modelControlType: modelControlType
modelControlType: modelControlType,
drag: drag
});
// 配置拖拽功能
if (drag) {
this.mainApp.appModelDrag?.configureDrag(modelName, drag);
}
// 更新 GameManager 的字典
this.mainApp.gameManager?.updateDictionaries();
@ -274,7 +283,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 } = models[i];
const { modelId, modelUrl, modelControlType, drag } = models[i];
const result = await this.loadSingleModel(modelUrl, (event) => {
this.emitProgress(i, total, modelUrl, event);
@ -288,8 +297,14 @@ export class AppModel extends Monobehiver {
this.modelMetadataDic.Set(modelId, {
modelId: modelId,
modelUrl: modelUrl,
modelControlType: modelControlType
modelControlType: modelControlType,
drag: drag
});
// 配置拖拽功能
if (drag) {
this.mainApp.appModelDrag?.configureDrag(modelId, drag);
}
}
results.push(result);
@ -410,7 +425,8 @@ export class AppModel extends Monobehiver {
return await this.addSingle(
modelConfig.modelId,
modelConfig.modelUrl,
modelConfig.modelControlType
modelConfig.modelControlType,
modelConfig.drag
);
}

View File

@ -0,0 +1,257 @@
import { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';
import { PointerDragBehavior } from '@babylonjs/core/Behaviors/Meshes/pointerDragBehavior';
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
import { Scene } from '@babylonjs/core/scene';
import { Monobehiver } from '../base/Monobehiver';
/**
* 拖拽配置接口
*/
export interface DragConfig {
enable: boolean;
axis?: 'x' | 'y' | 'z' | 'xy' | 'xz' | 'yz' | 'xyz';
step?: number;
}
/**
* 模型拖拽信息
*/
interface ModelDragInfo {
config: DragConfig;
behavior: PointerDragBehavior | null;
currentAxis: 'x' | 'y' | 'z' | null;
}
/**
* 模型拖拽管理器 - 负责处理模型的拖拽交互
*/
export class AppModelDrag extends Monobehiver {
private modelDragMap: Map<string, ModelDragInfo>;
private scene: Scene | null;
constructor(mainApp: any) {
super(mainApp);
this.modelDragMap = new Map();
this.scene = null;
}
/**
* 初始化拖拽管理器
*/
Awake(): void {
this.scene = this.mainApp.appScene.object;
if (!this.scene) {
console.warn('Scene not initialized');
return;
}
}
/**
* 为模型配置拖拽
* @param modelId 模型ID
* @param config 拖拽配置
*/
configureDrag(modelId: string, config: DragConfig): void {
// 获取模型的根网格
const meshes = this.mainApp.appModel?.modelDic?.Get(modelId);
if (!meshes || !meshes.length) {
console.warn(`Model not found: ${modelId}`);
return;
}
const rootMesh = meshes[0]; // 第一个是根节点
// 如果已存在,先移除旧的行为
const existingInfo = this.modelDragMap.get(modelId);
if (existingInfo?.behavior) {
rootMesh.removeBehavior(existingInfo.behavior);
}
// 创建拖拽信息
const dragInfo: ModelDragInfo = {
config: { ...config },
behavior: null,
currentAxis: this.getFirstAvailableAxis(config.axis || 'xyz')
};
if (config.enable) {
// 创建并配置拖拽行为
dragInfo.behavior = this.createDragBehavior(modelId, dragInfo);
rootMesh.addBehavior(dragInfo.behavior);
}
this.modelDragMap.set(modelId, dragInfo);
}
/**
* 创建拖拽行为
*/
private createDragBehavior(modelId: string, dragInfo: ModelDragInfo): PointerDragBehavior {
const axis = dragInfo.currentAxis;
let dragAxis: Vector3;
// 根据当前激活的轴创建拖拽向量
switch (axis) {
case 'x':
dragAxis = new Vector3(1, 0, 0);
break;
case 'y':
dragAxis = new Vector3(0, 1, 0);
break;
case 'z':
dragAxis = new Vector3(0, 0, 1);
break;
default:
dragAxis = new Vector3(1, 0, 0);
}
// 创建拖拽行为
const pointerDragBehavior = new PointerDragBehavior({ dragAxis: dragAxis });
// 使用世界坐标系而不是物体本地坐标系
pointerDragBehavior.useObjectOrientationForDragging = false;
// 监听拖拽开始事件
pointerDragBehavior.onDragStartObservable.add(() => {
// 禁用相机控制
this.disableCameraControl();
});
// 监听拖拽结束事件
pointerDragBehavior.onDragEndObservable.add(() => {
// 恢复相机控制
this.enableCameraControl();
});
return pointerDragBehavior;
}
/**
* 获取模型的拖拽配置
* @param modelId 模型ID
*/
getDragConfig(modelId: string): DragConfig | undefined {
return this.modelDragMap.get(modelId)?.config;
}
/**
* 启用/禁用模型拖拽
* @param modelId 模型ID
* @param enable 是否启用
*/
setDragEnabled(modelId: string, enable: boolean): void {
const dragInfo = this.modelDragMap.get(modelId);
if (!dragInfo) return;
dragInfo.config.enable = enable;
const meshes = this.mainApp.appModel?.modelDic?.Get(modelId);
if (!meshes || !meshes.length) return;
const rootMesh = meshes[0];
if (enable) {
// 启用:创建并添加行为
if (!dragInfo.behavior) {
dragInfo.behavior = this.createDragBehavior(modelId, dragInfo);
rootMesh.addBehavior(dragInfo.behavior);
}
} else {
// 禁用:移除行为
if (dragInfo.behavior) {
rootMesh.removeBehavior(dragInfo.behavior);
dragInfo.behavior = null;
}
}
}
/**
* 切换激活的轴向
* @param modelId 模型ID
* @param axis 要激活的轴向
*/
switchAxis(modelId: string, axis: 'x' | 'y' | 'z'): void {
const dragInfo = this.modelDragMap.get(modelId);
if (!dragInfo) return;
// 检查该轴是否在允许的轴向中
if (!this.isAxisAllowed(axis, dragInfo.config.axis || 'xyz')) {
console.warn(`Axis ${axis} is not allowed for model ${modelId}`);
return;
}
// 更新当前轴
dragInfo.currentAxis = axis;
// 重新创建拖拽行为
const meshes = this.mainApp.appModel?.modelDic?.Get(modelId);
if (!meshes || !meshes.length) return;
const rootMesh = meshes[0];
// 移除旧行为
if (dragInfo.behavior) {
rootMesh.removeBehavior(dragInfo.behavior);
}
// 创建新行为
if (dragInfo.config.enable) {
dragInfo.behavior = this.createDragBehavior(modelId, dragInfo);
rootMesh.addBehavior(dragInfo.behavior);
}
}
/**
* 获取配置中的第一个可用轴
*/
private getFirstAvailableAxis(axisConfig: string): 'x' | 'y' | 'z' | null {
if (axisConfig.includes('x')) return 'x';
if (axisConfig.includes('y')) return 'y';
if (axisConfig.includes('z')) return 'z';
return null;
}
/**
* 检查轴是否在允许的配置中
*/
private isAxisAllowed(axis: 'x' | 'y' | 'z', axisConfig: string): boolean {
return axisConfig.includes(axis);
}
/**
* 禁用相机控制
*/
private disableCameraControl(): void {
const camera = this.mainApp.appCamera?.object;
if (camera) {
camera.detachControl();
}
}
/**
* 启用相机控制
*/
private enableCameraControl(): void {
const camera = this.mainApp.appCamera?.object;
const canvas = this.mainApp.appEngin?.object?.getRenderingCanvas();
if (camera && canvas) {
camera.attachControl(canvas, true);
}
}
/**
* 清理资源
*/
dispose(): void {
// 移除所有拖拽行为
this.modelDragMap.forEach((dragInfo, modelId) => {
if (dragInfo.behavior) {
const meshes = this.mainApp.appModel?.modelDic?.Get(modelId);
if (meshes && meshes.length) {
meshes[0].removeBehavior(dragInfo.behavior);
}
}
});
this.modelDragMap.clear();
}
}

View File

@ -746,42 +746,70 @@ export class GameManager extends Monobehiver {
}
/**
* 应用材质
* @param target 目标对象
* @param material 材质路径
* 应用材质属性
* @param options 材质配置选项
*/
applyMaterial(target: string, attribute: string, value:string): void {
if (attribute !== 'baseColor' || typeof value !== 'string') return;
applyMaterial(options: {
target: string;
albedoColor?: string;
albedoTexture?: string;
normalMap?: string;
metallicTexture?: string;
roughness?: number;
metallic?: number;
}): void {
this.updateDictionaries();
// 示例实现:根据目标和材质路径应用材质
// 1. 查找目标网格
// 1. 查找目标材质
const targetMaterials: PBRMaterial[] = [];
this.materialDic.Values().forEach(material => {
if (material.name === target) {
if (material.name === options.target) {
targetMaterials.push(material);
}
});
if (targetMaterials.length === 0) {
console.warn(`Target not found: ${target}`);
console.warn(`Material not found: ${options.target}`);
return;
}
// 2. 处理材质路径
// 这里可以根据材质路径加载对应的材质配置
// 例如paint/blue 可以映射到特定的材质配置
// 3. 应用材质到目标网格
const color = Color3.FromHexString(value);
// 2. 应用材质属性到目标材质
targetMaterials.forEach(material => {
// 如果是 baseColor 且值是字符串16进制颜色转换为 Color3
// 应用颜色
if (options.albedoColor) {
const color = Color3.FromHexString(options.albedoColor);
material.albedoColor.copyFrom(color);
}
// 如果有纹理颜色会作为纹理的乘法因子
// 强制刷新材质
material.markDirty();
// 应用反照率纹理颜色贴图)
if (options.albedoTexture) {
material.albedoTexture = new Texture(options.albedoTexture);
}
// 应用法线贴图
if (options.normalMap) {
material.bumpTexture = new Texture(options.normalMap);
}
// 应用金属度贴图
if (options.metallicTexture) {
material.metallicTexture = new Texture(options.metallicTexture);
}
// 应用粗糙度值
if (options.roughness !== undefined) {
material.roughness = options.roughness;
}
// 应用金属度值
if (options.metallic !== undefined) {
material.metallic = options.metallic;
}
// 强制刷新材质
material.markDirty();
});
console.log(`Material applied to: ${options.target}`, options);
}
}

View File

@ -17,6 +17,8 @@ import { AppHotspot } from './AppHotspot';
import { AppDomTo3D } from './AppDomTo3D';
import { AppSelectionOutline } from './AppSelectionOutline';
import { AppPositionGizmo } from './AppPositionGizmo';
import { AppModelDrag } from './AppModelDrag';
import { AppDropZone } from './AppDropZone';
/**
* 主应用类 - 3D场景的核心控制器
@ -34,6 +36,8 @@ export class MainApp {
appDomTo3D: AppDomTo3D;
appSelectionOutline: AppSelectionOutline;
appPositionGizmo: AppPositionGizmo;
appModelDrag: AppModelDrag;
appDropZone: AppDropZone;
gameManager: GameManager;
@ -49,6 +53,7 @@ export class MainApp {
this.appDomTo3D = new AppDomTo3D(this);
this.appSelectionOutline = new AppSelectionOutline(this);
this.appPositionGizmo = new AppPositionGizmo(this);
this.appModelDrag = new AppModelDrag(this);
this.gameManager = new GameManager(this);
window.addEventListener("resize", () => this.appEngin.handleResize());
@ -85,8 +90,11 @@ export class MainApp {
this.appRay.Awake();
this.appSelectionOutline.init();
this.appPositionGizmo.Awake();
this.appModelDrag.Awake();
this.appDomTo3D.init();
this.appModel.initManagers();
// 在场景创建后初始化 AppDropZone
this.appDropZone = new AppDropZone(this.appScene.object);
this.update();
EventBridge.sceneReady({ scene: this.appScene.object });
}
@ -106,6 +114,7 @@ export class MainApp {
this.appModel?.clean();
this.appEnv?.clean();
this.appPositionGizmo?.dispose();
this.appModelDrag?.dispose();
// this.appHotspot?.clear();
}
}