This commit is contained in:
2026-05-27 10:31:17 +08:00
parent dbde91bbe0
commit a8ae4ffc57
3 changed files with 106 additions and 175 deletions

View File

@ -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,

View File

@ -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];
// 处理超出边界的情况开关2returnWhenOutOfBounds
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];
// 检查目标区域是否已被其他模型占用开关2handleOccupiedZone
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(`[边界检测] 未启用替换模式或未启用占用区域处理,允许重叠`);
}
}

View File

@ -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);