import { type IPointerEvent, PickingInfo, PointerEventTypes, Vector3, AbstractMesh, Color3, PBRMaterial, StandardMaterial, HighlightLayer, PointerInfo, ElasticEase, } from '@babylonjs/core' import { MainApp } from './MainApp' import { Monobehiver } from '../base/Monobehiver'; import { EventBridge } from '../event/bridge'; class AppRay extends Monobehiver { oldPoint: Vector3 = Vector3.Zero() newPoint: Vector3 = Vector3.Zero() 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) } Awake() { this.setupHighlightLayer() this.setupUnifiedEventHandling() } // 设置高亮层 setupHighlightLayer() { // 高亮层创建已禁用 return } // 设置统一的事件处理 setupUnifiedEventHandling() { // 使用观察者模式而不是直接覆盖事件处理器 this.mainApp.appScene.object.onPointerObservable.add((pointerInfo: PointerInfo) => { const { type, event, pickInfo } = pointerInfo; // 检查事件类型并转换 const pointerEvent = event as IPointerEvent; // 只处理鼠标和触摸事件 if (pointerEvent.pointerType !== "mouse" && pointerEvent.pointerType !== "touch") { return; } // 处理非主要触摸点 if (pointerEvent.pointerType === "touch" && (pointerEvent as any).isPrimary === false) { return; } 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 (this.isLongPress) { console.log('[长按] 松手,隐藏分割区域'); this.mainApp.appDropZone.hide(); } // 只有在没有移动且不是长按的情况下才处理单击 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 热点) // if (pickInfo && pickInfo.pickedMesh) { // const isHotspotClick = this.mainApp.appHotspot?.handlePick(pickInfo.pickedMesh); // if (isHotspotClick) return; // } // const isSpriteHotspotClick = this.mainApp.appHotspot?.handleSpritePick(); // if (isSpriteHotspotClick) return; if (pickInfo && pickInfo.hit && pickInfo.pickedMesh && pickInfo.pickedPoint) { // 检查是否点击的是热点 if (pickInfo.pickedMesh.metadata?.type === 'hotspot') { return; } // 检查是否点击的是放置区域 if (pickInfo.pickedMesh.name.startsWith('placement_')) { const zones = this.mainApp.appDropZone.getPlacementZones(); const clickedZone = zones.find(zone => zone.mesh === pickInfo.pickedMesh); if (clickedZone) { // 计算该放置区域的目标位置和旋转 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); EventBridge.dropZoneClick({ wallName: clickedZone.wallName, index: clickedZone.index, center: clickedZone.center, width: clickedZone.width, height: clickedZone.height, normal: clickedZone.normal, mesh: clickedZone.mesh, transform: { position: { x: targetPosition.x, y: targetPosition.y, z: targetPosition.z }, rotation: { x: 0, y: angle * 180 / Math.PI, z: 0 }, scale: { x: 1, y: 1, z: 1 } } }); return; } } 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); if (holdingShift) { this.mainApp.appSelectionOutline.toggle(modelMeshes); } else { this.mainApp.appSelectionOutline.select(modelMeshes); } const transformTarget = this.mainApp.appModel.getModelTransformTargetByMesh(pickInfo.pickedMesh); this.mainApp.appPositionGizmo.attach(transformTarget ?? pickInfo.pickedMesh); // 获取模型元数据 const modelMetadata = this.mainApp.appModel.getMetadataByMesh(pickInfo.pickedMesh); // 获取模型名称(优先使用 modelName,如果没有则使用 modelId) const modelName = this.mainApp.appModel.findModelNameByMesh(pickInfo.pickedMesh); console.log(modelName); EventBridge.modelClick({ meshName: pickInfo.pickedMesh.name, modelName: modelName, pickedMesh: pickInfo.pickedMesh, pickedPoint: pickInfo.pickedPoint, materialName: materialName, modelControlType: modelMetadata?.modelControlType, }); } else { this.mainApp.appSelectionOutline.clear(); this.mainApp.appPositionGizmo.detach(); this.mainApp.appDomTo3D.hideAll(); // 隐藏放置区域 this.mainApp.appDropZone?.hide(); } } // 高亮显示网格 - 已禁用 highlightMesh(mesh: AbstractMesh) { // 高亮功能已禁用 return } // 使用材质方式高亮 - 已禁用 highlightWithMaterial(mesh: AbstractMesh) { // 材质高亮功能已禁用 return } // 清除高亮 clearHighlight() { try { // 清除高亮层 if (this.highlightLayer && this.highlightedMesh) { try { this.highlightLayer.removeMesh(this.highlightedMesh as any) } catch (error) { console.warn('高亮层移除失败:', error) } } // 恢复原始材质 if (this.highlightedMesh && this.originalMaterial) { const material = this.highlightedMesh.material as PBRMaterial if (material && this.originalMaterial.albedoColor) { material.albedoColor = this.originalMaterial.albedoColor material.emissiveColor = this.originalMaterial.emissiveColor } } this.highlightedMesh = null this.originalMaterial = null } catch (error) { console.error('清除高亮失败:', error) } } /** * 渲染热点 * @param hotspots 热点数据 */ renderHotspots(hotspots: any[]): void { this.mainApp.appHotspot?.render(hotspots); } } export { AppRay }