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; snapToZone?: boolean; // 拖拽吸附:松开时自动吸附到最近的分割区域 returnWhenOutOfBounds?: boolean; // 拖拽到区域外时返回原位置 handleOccupiedZone?: boolean; // 拖拽到已占用区域时的处理:true=返回原位置或替换,false=允许重叠 occupiedZoneAction?: 'return' | 'replace'; // 当 handleOccupiedZone=true 时的具体行为:'return' 返回原位置,'replace' 替换目标位置的模型 } /** * 模型拖拽信息 */ interface ModelDragInfo { config: DragConfig; behavior: PointerDragBehavior | null; currentAxis: 'x' | 'y' | 'z' | null; } /** * 模型拖拽管理器 - 负责处理模型的拖拽交互 */ export class AppModelDrag extends Monobehiver { private modelDragMap: Map; 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; // 记录拖拽起始位置和状态 let dragStartPosition: Vector3 | null = null; let hasShownZones = false; // 是否已显示分割区域 // 监听拖拽开始事件 pointerDragBehavior.onDragStartObservable.add(() => { // 记录起始位置 const meshes = this.mainApp.appModel?.modelDic?.Get(modelId); if (meshes && meshes.length > 0) { dragStartPosition = meshes[0].position.clone(); } // 禁用相机控制 this.disableCameraControl(); // 不在这里显示分割区域,等到实际拖动时再显示 hasShownZones = false; }); // 监听拖拽中事件(用于延迟显示分割区域) pointerDragBehavior.onDragObservable.add((event) => { // 检查是否实际移动了 const meshes = this.mainApp.appModel?.modelDic?.Get(modelId); if (meshes && meshes.length > 0 && dragStartPosition) { const distance = Vector3.Distance(dragStartPosition, meshes[0].position); // 如果移动距离超过阈值且还没显示分割区域,则显示 if (distance > 0.01 && !hasShownZones && dragInfo.config.snapToZone) { this.showZonesForModel(modelId); hasShownZones = true; } } }); // 监听拖拽结束事件 pointerDragBehavior.onDragEndObservable.add(() => { // 检查是否实际移动了 const meshes = this.mainApp.appModel?.modelDic?.Get(modelId); let hasMoved = false; if (meshes && meshes.length > 0 && dragStartPosition) { const distance = Vector3.Distance(dragStartPosition, meshes[0].position); hasMoved = distance > 0.01; // 移动距离大于 0.01 才算拖拽 } // 恢复相机控制 this.enableCameraControl(); // 只有在实际移动的情况下才执行拖拽逻辑 if (hasMoved) { // 如果启用了拖拽吸附,隐藏分割区域并吸附到最近区域 if (dragInfo.config.snapToZone && hasShownZones) { this.hideZonesForModel(modelId); this.snapModelToZone(modelId); } else { // 否则只更新映射关系 this.updateModelZoneMapping(modelId); } } // 清除状态 dragStartPosition = null; hasShownZones = false; }); 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); } } /** * 显示模型所在墙面的分割区域 * @param modelId 模型ID */ private showZonesForModel(modelId: string): void { const appDropZone = this.mainApp.appDropZone; if (!appDropZone) return; // 查找模型所在的墙面 let wallName: string | null = null; appDropZone['zoneModelMap']?.forEach((id: string, zoneKey: string) => { if (id === modelId) { const match = zoneKey.match(/^(.+)\[(\d+)\]$/); if (match) { wallName = match[1]; } } }); if (wallName) { console.log(`[拖拽吸附] 显示墙面 ${wallName} 的分割区域`); // 只显示该墙面的分割区域 appDropZone.showWall(wallName); } } /** * 隐藏分割区域 * @param modelId 模型ID */ private hideZonesForModel(modelId: string): void { const appDropZone = this.mainApp.appDropZone; if (!appDropZone) return; console.log(`[拖拽吸附] 隐藏分割区域`); appDropZone.hide(); } /** * 将模型吸附到最近的分割区域 * @param modelId 模型ID */ private snapModelToZone(modelId: string): void { const meshes = this.mainApp.appModel?.modelDic?.Get(modelId); if (!meshes || !meshes.length) return; const rootMesh = meshes[0]; const appDropZone = this.mainApp.appDropZone; if (!appDropZone) return; // 查找模型原来所在的墙面和区域索引 let wallName: string | null = null; let originalZoneIndex: number = -1; appDropZone['zoneModelMap']?.forEach((id: string, zoneKey: string) => { if (id === modelId) { const match = zoneKey.match(/^(.+)\[(\d+)\]$/); if (match) { wallName = match[1]; originalZoneIndex = parseInt(match[2]); } } }); if (!wallName) return; // 获取该墙面的所有分割区域 const wallZones = appDropZone.getZonesByWall(wallName); if (!wallZones.length) return; // 找到最近的区域 let closestZoneIndex = -1; let minDistance = Number.POSITIVE_INFINITY; wallZones.forEach((zone, index) => { const distance = rootMesh.position.subtract(zone.center).length(); if (distance < minDistance) { minDistance = distance; closestZoneIndex = index; } }); if (closestZoneIndex === -1) return; // 获取拖拽配置 const dragInfo = this.modelDragMap.get(modelId); const returnWhenOutOfBounds = dragInfo?.config.returnWhenOutOfBounds ?? false; const handleOccupiedZone = dragInfo?.config.handleOccupiedZone ?? false; const occupiedZoneAction = dragInfo?.config.occupiedZoneAction ?? 'return'; // 检查是否拖拽到区域外 let isOutOfBounds = false; // 计算墙面的边界 let minX = Number.POSITIVE_INFINITY; let maxX = Number.NEGATIVE_INFINITY; let minZ = Number.POSITIVE_INFINITY; let maxZ = Number.NEGATIVE_INFINITY; wallZones.forEach(zone => { const halfWidth = zone.width / 2; // 根据法线方向计算边界 if (Math.abs(zone.normal.x) > 0.5) { // 左右墙面(法线沿X轴) minZ = Math.min(minZ, zone.center.z - halfWidth); maxZ = Math.max(maxZ, zone.center.z + halfWidth); } else if (Math.abs(zone.normal.z) > 0.5) { // 前后墙面(法线沿Z轴) minX = Math.min(minX, zone.center.x - halfWidth); maxX = Math.max(maxX, zone.center.x + halfWidth); } }); // 检查当前位置是否在边界内 const currentPos = rootMesh.position; if (minX !== Number.POSITIVE_INFINITY && maxX !== Number.NEGATIVE_INFINITY) { if (currentPos.x < minX || currentPos.x > maxX) { isOutOfBounds = true; } } if (minZ !== Number.POSITIVE_INFINITY && maxZ !== Number.NEGATIVE_INFINITY) { if (currentPos.z < minZ || currentPos.z > maxZ) { isOutOfBounds = true; } } // 处理超出边界的情况(开关2:returnWhenOutOfBounds) if (isOutOfBounds) { console.log(`[拖拽吸附] 模型 ${modelId} 超出边界`); if (returnWhenOutOfBounds) { // 启用了边界返回,回到原来的区域 console.log(`[拖拽吸附] 启用边界返回,回到原区域 ${originalZoneIndex}`); if (originalZoneIndex !== -1) { const originalZone = wallZones[originalZoneIndex]; if (originalZone) { const offsetDistance = 0; const returnPosition = originalZone.center.add(originalZone.normal.scale(offsetDistance)); rootMesh.position.copyFrom(returnPosition); const targetDirection = originalZone.normal.scale(-1); const angle = Math.atan2(targetDirection.x, targetDirection.z); rootMesh.rotation.y = angle; console.log(`[拖拽吸附] 模型 ${modelId} 已返回原区域 ${originalZoneIndex}`); return; // 不更新映射,保持原映射 } } } else { // 未启用边界返回,保持当前位置,不做吸附 console.log(`[拖拽吸附] 未启用边界返回,保持当前位置,不做吸附`); // 更新映射关系(可能移出了原区域) this.updateModelZoneMapping(modelId); return; } } const targetZone = wallZones[closestZoneIndex]; // 检查目标区域是否已被其他模型占用(开关2:handleOccupiedZone) const targetZoneKey = `${wallName}[${closestZoneIndex}]`; const occupyingModelId = appDropZone['zoneModelMap']?.get(targetZoneKey); if (occupyingModelId && occupyingModelId !== modelId) { // 目标区域已被其他模型占用 console.log(`[拖拽吸附] 目标区域 ${closestZoneIndex} 已被模型 ${occupyingModelId} 占用`); if (handleOccupiedZone) { // 启用了占用区域处理 if (occupiedZoneAction === 'return') { // 返回原位置 console.log(`[拖拽吸附] 配置为返回原位置,回到区域 ${originalZoneIndex}`); if (originalZoneIndex !== -1) { const originalZone = wallZones[originalZoneIndex]; if (originalZone) { const offsetDistance = 0; const returnPosition = originalZone.center.add(originalZone.normal.scale(offsetDistance)); rootMesh.position.copyFrom(returnPosition); const targetDirection = originalZone.normal.scale(-1); const angle = Math.atan2(targetDirection.x, targetDirection.z); rootMesh.rotation.y = angle; console.log(`[拖拽吸附] 模型 ${modelId} 返回原区域 ${originalZoneIndex}`); return; // 不更新映射,保持原映射 } } } else if (occupiedZoneAction === 'replace') { // 替换目标位置的模型(继续执行后面的逻辑) console.log(`[拖拽吸附] 配置为替换模型,将替换模型 ${occupyingModelId}`); } } else { // 未启用占用区域处理,允许重叠(继续执行后面的逻辑) console.log(`[拖拽吸附] 未启用占用区域处理,允许重叠`); } } // 计算吸附位置(区域中心 + 法线偏移) const offsetDistance = 0; const snapPosition = targetZone.center.add(targetZone.normal.scale(offsetDistance)); // 吸附到目标位置 rootMesh.position.copyFrom(snapPosition); // 更新旋转 const targetDirection = targetZone.normal.scale(-1); const angle = Math.atan2(targetDirection.x, targetDirection.z); rootMesh.rotation.y = angle; console.log(`[拖拽吸附] 模型 ${modelId} 吸附到区域 ${closestZoneIndex}`); // 更新映射关系 this.updateModelZoneMapping(modelId); } /** * 更新模型所属的分割区域映射 * @param modelId 模型ID */ private updateModelZoneMapping(modelId: string): void { const meshes = this.mainApp.appModel?.modelDic?.Get(modelId); if (!meshes || !meshes.length) return; const rootMesh = meshes[0]; const modelPosition = rootMesh.position; console.log(`[边界检测] 模型 ${modelId} 拖拽结束,当前位置:`, modelPosition); // 获取 AppDropZone const appDropZone = this.mainApp.appDropZone; if (!appDropZone) return; // 查找该模型原本所在的墙面 let originalWallName: string | null = null; appDropZone['zoneModelMap']?.forEach((id: string, zoneKey: string) => { if (id === modelId) { const match = zoneKey.match(/^(.+)\[(\d+)\]$/); if (match) { originalWallName = match[1]; } } }); if (!originalWallName) { console.log(`[边界检测] 模型 ${modelId} 未找到原始墙面,跳过检测`); return; } console.log(`[边界检测] 模型 ${modelId} 原始墙面: ${originalWallName}`); // 获取该墙面的所有分割区域 const wallZones = appDropZone.getZonesByWall(originalWallName); if (!wallZones.length) return; console.log(`[边界检测] 墙面 ${originalWallName} 有 ${wallZones.length} 个分割区域`); // 计算模型与每个分割区域的距离,找到最近的区域 let closestZoneIndex = -1; let minDistance = Number.POSITIVE_INFINITY; wallZones.forEach((zone, index) => { // 计算模型位置到区域中心的距离 const distance = modelPosition.subtract(zone.center).length(); console.log(`[边界检测] 区域 ${index} 中心:`, zone.center, `距离: ${distance.toFixed(3)}`); if (distance < minDistance) { minDistance = distance; closestZoneIndex = index; } }); if (closestZoneIndex === -1) { console.log(`[边界检测] 未找到最近的区域`); return; } console.log(`[边界检测] 模型 ${modelId} 最接近区域 ${closestZoneIndex},距离: ${minDistance.toFixed(3)}`); // 查找模型当前所在的区域索引 let currentZoneIndex = -1; appDropZone['zoneModelMap']?.forEach((id: string, zoneKey: string) => { if (id === modelId) { const match = zoneKey.match(/^.+\[(\d+)\]$/); if (match) { currentZoneIndex = parseInt(match[1]); } } }); // 如果模型移动到了新的区域,更新映射 if (currentZoneIndex !== closestZoneIndex) { console.log(`[边界检测] 模型 ${modelId} 从区域 ${currentZoneIndex} 移动到区域 ${closestZoneIndex}`); // 删除旧映射 if (currentZoneIndex !== -1) { const oldKey = `${originalWallName}[${currentZoneIndex}]`; appDropZone['zoneModelMap']?.delete(oldKey); console.log(`[边界检测] 删除旧映射: ${oldKey}`); } // 检查目标区域是否已有模型 const newKey = `${originalWallName}[${closestZoneIndex}]`; const existingModelId = appDropZone['zoneModelMap']?.get(newKey); // 获取拖拽配置 const dragInfo = this.modelDragMap.get(modelId); const handleOccupiedZone = dragInfo?.config.handleOccupiedZone ?? false; const occupiedZoneAction = dragInfo?.config.occupiedZoneAction ?? 'return'; if (existingModelId && existingModelId !== modelId) { console.log(`[边界检测] 目标区域 ${closestZoneIndex} 已有模型 ${existingModelId}`); // 只有在启用占用区域处理且为 'replace' 模式下才交换位置 if (handleOccupiedZone && occupiedZoneAction === 'replace') { console.log(`[边界检测] 配置为替换模式,交换位置`); // 将原有模型移动到旧位置 if (currentZoneIndex !== -1) { const swapKey = `${originalWallName}[${currentZoneIndex}]`; appDropZone['zoneModelMap']?.set(swapKey, existingModelId); console.log(`[边界检测] 模型 ${existingModelId} 移动到区域 ${currentZoneIndex}`); // 实际移动被替换模型的物理位置 const existingMeshes = this.mainApp.appModel?.modelDic?.Get(existingModelId); if (existingMeshes && existingMeshes.length) { const existingRootMesh = existingMeshes[0]; const swapZone = wallZones[currentZoneIndex]; if (swapZone) { const offsetDistance = 0; const swapPosition = swapZone.center.add(swapZone.normal.scale(offsetDistance)); existingRootMesh.position.copyFrom(swapPosition); // 更新旋转 const targetDirection = swapZone.normal.scale(-1); const angle = Math.atan2(targetDirection.x, targetDirection.z); existingRootMesh.rotation.y = angle; console.log(`[边界检测] 已将模型 ${existingModelId} 物理移动到区域 ${currentZoneIndex}`); } } } } else { console.log(`[边界检测] 未启用替换模式或未启用占用区域处理,允许重叠`); } } // 添加新映射 appDropZone['zoneModelMap']?.set(newKey, modelId); console.log(`[边界检测] 添加新映射: ${newKey} -> ${modelId}`); } else { console.log(`[边界检测] 模型 ${modelId} 仍在区域 ${currentZoneIndex},无需更新映射`); } } /** * 清理资源 */ 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(); } }