309 lines
9.6 KiB
TypeScript
309 lines
9.6 KiB
TypeScript
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<string, string>;
|
||
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 }
|