diff --git a/src/babylonjs/AppDropZone.ts b/src/babylonjs/AppDropZone.ts index 4602473..a351de6 100644 --- a/src/babylonjs/AppDropZone.ts +++ b/src/babylonjs/AppDropZone.ts @@ -506,6 +506,8 @@ export class AppDropZone { */ show(): void { this.placementWall.show(); + // 禁用所有已放置模型的拾取 + this.setModelsPickable(false); } /** @@ -514,6 +516,8 @@ export class AppDropZone { */ showWall(wallName: string): void { this.placementWall.showWall(wallName); + // 禁用所有已放置模型的拾取 + this.setModelsPickable(false); } /** @@ -521,6 +525,25 @@ export class AppDropZone { */ hide(): void { this.placementWall.hide(); + // 恢复所有已放置模型的拾取 + this.setModelsPickable(true); + } + + /** + * 设置所有已放置模型的可拾取状态 + * @param pickable 是否可拾取 + */ + private setModelsPickable(pickable: boolean): void { + if (!this.appModel) return; + + this.zoneModelMap.forEach((modelId) => { + const meshes = this.appModel!.getCachedMeshes(modelId); + if (meshes && meshes.length > 0) { + meshes.forEach(mesh => { + mesh.isPickable = pickable; + }); + } + }); } /** diff --git a/src/babylonjs/AppModelDrag.ts b/src/babylonjs/AppModelDrag.ts index 4977510..7aebaeb 100644 --- a/src/babylonjs/AppModelDrag.ts +++ b/src/babylonjs/AppModelDrag.ts @@ -114,37 +114,73 @@ export class AppModelDrag extends Monobehiver { // 使用世界坐标系而不是物体本地坐标系 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(); - // 如果启用了拖拽吸附,显示分割区域 - if (dragInfo.config.snapToZone) { - this.showZonesForModel(modelId); + // 不在这里显示分割区域,等到实际拖动时再显示 + 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; + } + } + + // 应用边界限制 + if (dragInfo.config.boundaryConstraint) { + this.applyBoundaryConstraint(modelId, event.dragPlanePoint); } }); - // 监听拖拽中事件(用于边界限制) - if (dragInfo.config.boundaryConstraint) { - pointerDragBehavior.onDragObservable.add((event) => { - this.applyBoundaryConstraint(modelId, event.dragPlanePoint); - }); - } - // 监听拖拽结束事件 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 (dragInfo.config.snapToZone) { - this.hideZonesForModel(modelId); - this.snapModelToZone(modelId); - } else { - // 否则只更新映射关系 - this.updateModelZoneMapping(modelId); + // 只有在实际移动的情况下才执行拖拽逻辑 + if (hasMoved) { + // 如果启用了拖拽吸附,隐藏分割区域并吸附到最近区域 + if (dragInfo.config.snapToZone && hasShownZones) { + this.hideZonesForModel(modelId); + this.snapModelToZone(modelId); + } else { + // 否则只更新映射关系 + this.updateModelZoneMapping(modelId); + } } + + // 清除状态 + dragStartPosition = null; + hasShownZones = false; }); return pointerDragBehavior; diff --git a/src/babylonjs/AppRay.ts b/src/babylonjs/AppRay.ts index 2f63fc8..bcc5933 100644 --- a/src/babylonjs/AppRay.ts +++ b/src/babylonjs/AppRay.ts @@ -21,6 +21,11 @@ class AppRay extends Monobehiver { private highlightLayer: HighlightLayer | null = null private originalMaterial: any = null private highlightedMesh: AbstractMesh | null = null + private pointerDownTime: number = 0 + private pointerDownPickInfo: PickingInfo | null = null + private longPressTimer: any = null + private longPressThreshold: number = 500 // 长按阈值(毫秒) + private isLongPress: boolean = false constructor(mainApp: MainApp) { super(mainApp) @@ -58,18 +63,91 @@ class AppRay extends Monobehiver { if (type === PointerEventTypes.POINTERDOWN) { this.oldPoint.set(pointerEvent.clientX, 0, pointerEvent.clientY); + this.pointerDownTime = Date.now(); + this.pointerDownPickInfo = pickInfo; + this.isLongPress = false; + + // 清除之前的定时器 + if (this.longPressTimer) { + clearTimeout(this.longPressTimer); + } + + // 设置长按定时器 + this.longPressTimer = setTimeout(() => { + this.isLongPress = true; + this.handleLongPress(pointerEvent, this.pointerDownPickInfo); + }, this.longPressThreshold); + } else if (type === PointerEventTypes.POINTERUP) { + // 清除长按定时器 + if (this.longPressTimer) { + clearTimeout(this.longPressTimer); + this.longPressTimer = null; + } + this.newPoint.set(pointerEvent.clientX, 0, pointerEvent.clientY); const distance = Vector3.Distance(this.oldPoint, this.newPoint); - // 只有在没有移动的情况下才处理单击 - if (distance < 5) { // 增加一些容差 + // 只有在没有移动且不是长按的情况下才处理单击 + if (distance < 5 && !this.isLongPress) { this.handleSingleClick(pointerEvent, pickInfo); } + + this.isLongPress = false; + } else if (type === PointerEventTypes.POINTERMOVE) { + // 如果移动了,取消长按 + this.newPoint.set(pointerEvent.clientX, 0, pointerEvent.clientY); + const distance = Vector3.Distance(this.oldPoint, this.newPoint); + if (distance > 5 && this.longPressTimer) { + clearTimeout(this.longPressTimer); + this.longPressTimer = null; + } } }); } + // 处理长按 + handleLongPress(evt: IPointerEvent, pickInfo: PickingInfo | null) { + if (pickInfo && pickInfo.hit && pickInfo.pickedMesh) { + // 检查是否长按的是模型(不是放置区域、不是热点) + if (pickInfo.pickedMesh.metadata?.type === 'hotspot') { + return; + } + + if (pickInfo.pickedMesh.name.startsWith('placement_')) { + return; + } + + // 获取模型名称 + const modelName = this.mainApp.appModel.findModelNameByMesh(pickInfo.pickedMesh); + if (modelName) { + // 通过模型找到它所在的墙面 + const wallName = this.findModelWallName(modelName); + if (wallName) { + console.log(`[长按] 模型 ${modelName} 位于墙面 ${wallName},显示该墙面的放置区域`); + this.mainApp.appDropZone.showWall(wallName); + } + } + } + } + + // 查找模型所在的墙面 + private findModelWallName(modelId: string): string | null { + const zoneModelMap = this.mainApp.appDropZone['zoneModelMap'] as Map; + if (!zoneModelMap) return null; + + for (const [zoneKey, id] of zoneModelMap.entries()) { + if (id === modelId) { + // zoneKey 格式为 "wallName[index]" + const match = zoneKey.match(/^(.+)\[(\d+)\]$/); + if (match) { + return match[1]; + } + } + } + return null; + } + // 处理单击 handleSingleClick(evt: IPointerEvent, pickInfo: PickingInfo | null) { // 先尝试热点(mesh 热点 / sprite 热点) @@ -93,7 +171,7 @@ class AppRay extends Monobehiver { const clickedZone = zones.find(zone => zone.mesh === pickInfo.pickedMesh); if (clickedZone) { // 计算该放置区域的目标位置和旋转 - const offsetDistance = 0; // 增加偏移距离,让模型更往外 + const offsetDistance = 0; const targetPosition = clickedZone.center.add(clickedZone.normal.scale(offsetDistance)); const targetDirection = clickedZone.normal.scale(-1); const angle = Math.atan2(targetDirection.x, targetDirection.z); @@ -114,7 +192,7 @@ class AppRay extends Monobehiver { }, rotation: { x: 0, - y: angle * 180 / Math.PI, // 转换为角度 + y: angle * 180 / Math.PI, z: 0 }, scale: { @@ -130,6 +208,9 @@ class AppRay extends Monobehiver { this.mainApp.appDomTo3D.hideAll() + // 隐藏放置区域,避免遮挡配件模型的点击 + this.mainApp.appDropZone.hide(); + const materialName = pickInfo.pickedMesh.material?.name || ''; const holdingShift = Boolean((evt as any).shiftKey); const modelMeshes = this.mainApp.appModel.getModelMeshesByMesh(pickInfo.pickedMesh);