Files
zhengte.babylonjs-sdk/src/babylonjs/AppRay.ts
2026-05-27 10:31:17 +08:00

309 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 }