From a8ae4ffc57f5f33ee496fcede9bbcc364e0964f1 Mon Sep 17 00:00:00 2001 From: zguiy <1415466602@qq.com> Date: Wed, 27 May 2026 10:31:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.js | 7 +- src/babylonjs/AppModelDrag.ts | 268 ++++++++++++---------------------- src/babylonjs/AppRay.ts | 6 + 3 files changed, 106 insertions(+), 175 deletions(-) diff --git a/index.js b/index.js index 3de2649..2564b73 100644 --- a/index.js +++ b/index.js @@ -220,9 +220,10 @@ export const executeEvent = async (dropzone_data, result, sku) => { enable: true, axis: rotation.y === 0 || rotation.y === 180 ? 'x' : 'z', step: 0.1, - boundaryConstraint: true, // 启用边界限制 - snapToZone: false, // 启用拖拽吸附到最近的分割区域 - onOccupiedZone: '' // 拖拽到已占用区域时的行为:'return' 返回原位置,'replace' 替换目标位置的模型(默认 'return') "" 允许重叠 + // snapToZone: true, // 开关1:拖拽吸附到最近的分割区域 + // returnWhenOutOfBounds: false, // 开关2:拖拽到区域外时返回原位置 + // handleOccupiedZone: true, // 开关3:处理已占用区域(false=允许重叠) + // occupiedZoneAction: 'return' // 当开关3=true时的行为:'return'=返回原位置,'replace'=替换 }, transform: { position: position, diff --git a/src/babylonjs/AppModelDrag.ts b/src/babylonjs/AppModelDrag.ts index 3613435..6c41c97 100644 --- a/src/babylonjs/AppModelDrag.ts +++ b/src/babylonjs/AppModelDrag.ts @@ -11,9 +11,10 @@ export interface DragConfig { enable: boolean; axis?: 'x' | 'y' | 'z' | 'xy' | 'xz' | 'yz' | 'xyz'; step?: number; - boundaryConstraint?: boolean; // 边界限制:拖拽不得超出当前墙面的放置区域 snapToZone?: boolean; // 拖拽吸附:松开时自动吸附到最近的分割区域 - onOccupiedZone?: 'return' | 'replace' | ''; // 拖拽到已占用区域时的行为:'return' 返回原位置,'replace' 替换目标位置的模型,'' 什么都不做(允许重叠) + returnWhenOutOfBounds?: boolean; // 拖拽到区域外时返回原位置 + handleOccupiedZone?: boolean; // 拖拽到已占用区域时的处理:true=返回原位置或替换,false=允许重叠 + occupiedZoneAction?: 'return' | 'replace'; // 当 handleOccupiedZone=true 时的具体行为:'return' 返回原位置,'replace' 替换目标位置的模型 } /** @@ -133,7 +134,7 @@ export class AppModelDrag extends Monobehiver { hasShownZones = false; }); - // 监听拖拽中事件(用于边界限制和延迟显示分割区域) + // 监听拖拽中事件(用于延迟显示分割区域) pointerDragBehavior.onDragObservable.add((event) => { // 检查是否实际移动了 const meshes = this.mainApp.appModel?.modelDic?.Get(modelId); @@ -146,11 +147,6 @@ export class AppModelDrag extends Monobehiver { hasShownZones = true; } } - - // 应用边界限制 - if (dragInfo.config.boundaryConstraint) { - this.applyBoundaryConstraint(modelId, event.dragPlanePoint); - } }); // 监听拖拽结束事件 @@ -337,76 +333,6 @@ export class AppModelDrag extends Monobehiver { appDropZone.hide(); } - /** - * 应用边界限制 - * @param modelId 模型ID - * @param dragPoint 拖拽点位置 - */ - private applyBoundaryConstraint(modelId: string, dragPoint: Vector3): 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; - appDropZone['zoneModelMap']?.forEach((id: string, zoneKey: string) => { - if (id === modelId) { - const match = zoneKey.match(/^(.+)\[(\d+)\]$/); - if (match) { - wallName = match[1]; - } - } - }); - - if (!wallName) return; - - // 获取该墙面的所有分割区域 - const wallZones = appDropZone.getZonesByWall(wallName); - if (!wallZones.length) return; - - // 计算墙面的边界(所有分割区域的包围盒) - 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; - const halfHeight = zone.height / 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; - const constrainedPos = currentPos.clone(); - - if (minX !== Number.POSITIVE_INFINITY && maxX !== Number.NEGATIVE_INFINITY) { - constrainedPos.x = Math.max(minX, Math.min(maxX, currentPos.x)); - } - if (minZ !== Number.POSITIVE_INFINITY && maxZ !== Number.NEGATIVE_INFINITY) { - constrainedPos.z = Math.max(minZ, Math.min(maxZ, currentPos.z)); - } - - // 如果位置被限制,更新模型位置 - if (!constrainedPos.equals(currentPos)) { - rootMesh.position.copyFrom(constrainedPos); - console.log(`[边界限制] 模型 ${modelId} 位置被限制在边界内`); - } - } - /** * 将模型吸附到最近的分割区域 * @param modelId 模型ID @@ -454,97 +380,55 @@ export class AppModelDrag extends Monobehiver { // 获取拖拽配置 const dragInfo = this.modelDragMap.get(modelId); + const returnWhenOutOfBounds = dragInfo?.config.returnWhenOutOfBounds ?? false; + const handleOccupiedZone = dragInfo?.config.handleOccupiedZone ?? false; + const occupiedZoneAction = dragInfo?.config.occupiedZoneAction ?? 'return'; - // 如果启用了边界约束,检查模型是否在墙面边界内 - if (dragInfo?.config.boundaryConstraint) { - // 计算墙面的边界 - let minX = Number.POSITIVE_INFINITY; - let maxX = Number.NEGATIVE_INFINITY; - let minZ = Number.POSITIVE_INFINITY; - let maxZ = Number.NEGATIVE_INFINITY; + // 检查是否拖拽到区域外 + let isOutOfBounds = false; - wallZones.forEach(zone => { - const halfWidth = zone.width / 2; + // 计算墙面的边界 + let minX = Number.POSITIVE_INFINITY; + let maxX = Number.NEGATIVE_INFINITY; + let minZ = Number.POSITIVE_INFINITY; + let maxZ = Number.NEGATIVE_INFINITY; - // 根据法线方向计算边界 - 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); - } - }); + wallZones.forEach(zone => { + const halfWidth = zone.width / 2; - // 检查当前位置是否在边界内 - const currentPos = rootMesh.position; - let isOutOfBounds = false; - - if (minX !== Number.POSITIVE_INFINITY && maxX !== Number.NEGATIVE_INFINITY) { - if (currentPos.x < minX || currentPos.x > maxX) { - isOutOfBounds = true; - } + // 根据法线方向计算边界 + 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); } - if (minZ !== Number.POSITIVE_INFINITY && maxZ !== Number.NEGATIVE_INFINITY) { - if (currentPos.z < minZ || currentPos.z > maxZ) { - isOutOfBounds = true; - } + }); + + // 检查当前位置是否在边界内 + const currentPos = rootMesh.position; + + if (minX !== Number.POSITIVE_INFINITY && maxX !== Number.NEGATIVE_INFINITY) { + if (currentPos.x < minX || currentPos.x > maxX) { + isOutOfBounds = true; } - - // 如果超出边界,回到原来的区域 - if (isOutOfBounds && originalZoneIndex !== -1) { - console.log(`[拖拽吸附] 模型 ${modelId} 超出边界,检查原区域 ${originalZoneIndex}`); - - // 检查原区域是否已被其他模型占用 - const originalZoneKey = `${wallName}[${originalZoneIndex}]`; - const occupyingModelId = appDropZone['zoneModelMap']?.get(originalZoneKey); - - if (!occupyingModelId || occupyingModelId === modelId) { - // 原区域空闲或者就是当前模型,回到原区域 - closestZoneIndex = originalZoneIndex; - } else { - // 原区域已被占用,找最近的空闲区域 - console.log(`[拖拽吸附] 原区域已被模型 ${occupyingModelId} 占用,寻找空闲区域`); - - let foundFreeZone = false; - for (let i = 0; i < wallZones.length; i++) { - const zoneKey = `${wallName}[${i}]`; - const occupant = appDropZone['zoneModelMap']?.get(zoneKey); - - if (!occupant || occupant === modelId) { - // 找到空闲区域 - closestZoneIndex = i; - foundFreeZone = true; - console.log(`[拖拽吸附] 找到空闲区域 ${i}`); - break; - } - } - - // 如果没有空闲区域,保持在原区域(会触发交换逻辑) - if (!foundFreeZone) { - console.log(`[拖拽吸附] 没有空闲区域,回到原区域 ${originalZoneIndex}`); - closestZoneIndex = originalZoneIndex; - } - } + } + if (minZ !== Number.POSITIVE_INFINITY && maxZ !== Number.NEGATIVE_INFINITY) { + if (currentPos.z < minZ || currentPos.z > maxZ) { + isOutOfBounds = true; } } - const targetZone = wallZones[closestZoneIndex]; + // 处理超出边界的情况(开关2:returnWhenOutOfBounds) + if (isOutOfBounds) { + console.log(`[拖拽吸附] 模型 ${modelId} 超出边界`); - // 检查目标区域是否已被其他模型占用 - const targetZoneKey = `${wallName}[${closestZoneIndex}]`; - const occupyingModelId = appDropZone['zoneModelMap']?.get(targetZoneKey); - const onOccupiedZone = dragInfo?.config.onOccupiedZone || 'return'; - - if (occupyingModelId && occupyingModelId !== modelId) { - // 目标区域已被其他模型占用 - console.log(`[拖拽吸附] 目标区域 ${closestZoneIndex} 已被模型 ${occupyingModelId} 占用`); - - if (onOccupiedZone === 'return') { - // 返回原位置 - console.log(`[拖拽吸附] 配置为返回原位置,回到区域 ${originalZoneIndex}`); + if (returnWhenOutOfBounds) { + // 启用了边界返回,回到原来的区域 + console.log(`[拖拽吸附] 启用边界返回,回到原区域 ${originalZoneIndex}`); if (originalZoneIndex !== -1) { const originalZone = wallZones[originalZoneIndex]; if (originalZone) { @@ -556,16 +440,56 @@ export class AppModelDrag extends Monobehiver { const angle = Math.atan2(targetDirection.x, targetDirection.z); rootMesh.rotation.y = angle; - console.log(`[拖拽吸附] 模型 ${modelId} 返回原区域 ${originalZoneIndex}`); + console.log(`[拖拽吸附] 模型 ${modelId} 已返回原区域 ${originalZoneIndex}`); return; // 不更新映射,保持原映射 } } - } else if (onOccupiedZone === 'replace') { - // 替换目标位置的模型(继续执行后面的逻辑) - console.log(`[拖拽吸附] 配置为替换模型,将替换模型 ${occupyingModelId}`); - } else if (onOccupiedZone === '') { - // 什么都不做,允许重叠(继续执行后面的逻辑,但不会触发替换) - console.log(`[拖拽吸附] 配置为允许重叠,继续放置`); + } 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.05; + 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(`[拖拽吸附] 未启用占用区域处理,允许重叠`); } } @@ -678,13 +602,14 @@ export class AppModelDrag extends Monobehiver { // 获取拖拽配置 const dragInfo = this.modelDragMap.get(modelId); - const onOccupiedZone = dragInfo?.config.onOccupiedZone || 'return'; + const handleOccupiedZone = dragInfo?.config.handleOccupiedZone ?? false; + const occupiedZoneAction = dragInfo?.config.occupiedZoneAction ?? 'return'; if (existingModelId && existingModelId !== modelId) { console.log(`[边界检测] 目标区域 ${closestZoneIndex} 已有模型 ${existingModelId}`); - // 只有在 'replace' 模式下才交换位置 - if (onOccupiedZone === 'replace') { + // 只有在启用占用区域处理且为 'replace' 模式下才交换位置 + if (handleOccupiedZone && occupiedZoneAction === 'replace') { console.log(`[边界检测] 配置为替换模式,交换位置`); // 将原有模型移动到旧位置 @@ -712,9 +637,8 @@ export class AppModelDrag extends Monobehiver { } } } - } else if (onOccupiedZone === '') { - // 允许重叠,不做任何处理 - console.log(`[边界检测] 配置为允许重叠,不交换位置`); + } else { + console.log(`[边界检测] 未启用替换模式或未启用占用区域处理,允许重叠`); } } diff --git a/src/babylonjs/AppRay.ts b/src/babylonjs/AppRay.ts index bcc5933..32051ed 100644 --- a/src/babylonjs/AppRay.ts +++ b/src/babylonjs/AppRay.ts @@ -88,6 +88,12 @@ class AppRay extends Monobehiver { this.newPoint.set(pointerEvent.clientX, 0, pointerEvent.clientY); const distance = Vector3.Distance(this.oldPoint, this.newPoint); + // 如果是长按后松手,隐藏分割区域 + if (this.isLongPress) { + console.log('[长按] 松手,隐藏分割区域'); + this.mainApp.appDropZone.hide(); + } + // 只有在没有移动且不是长按的情况下才处理单击 if (distance < 5 && !this.isLongPress) { this.handleSingleClick(pointerEvent, pickInfo);