199 lines
6.0 KiB
TypeScript
199 lines
6.0 KiB
TypeScript
import { Vector3, Matrix } from '@babylonjs/core/Maths/math.vector';
|
||
import { Scene } from '@babylonjs/core/scene';
|
||
import { Camera } from '@babylonjs/core/Cameras/camera';
|
||
import { Monobehiver } from '../base/Monobehiver';
|
||
import { PointerEventTypes, PointerInfo } from '@babylonjs/core';
|
||
|
||
interface DomElement {
|
||
dom: HTMLElement;
|
||
position: Vector3;
|
||
offset?: { x: number; y: number };
|
||
visible: boolean;
|
||
}
|
||
|
||
/**
|
||
* DOM 2D转3D坐标管理类
|
||
* 将DOM元素固定在3D场景的特定坐标上
|
||
*/
|
||
export class AppDomTo3D extends Monobehiver {
|
||
private domElements: Map<string, DomElement>;
|
||
private scene: Scene | null;
|
||
private camera: Camera | null;
|
||
private pointerDownPos: Vector3;
|
||
private pointerUpPos: Vector3;
|
||
|
||
constructor(mainApp: any) {
|
||
super(mainApp);
|
||
this.domElements = new Map();
|
||
this.scene = null;
|
||
this.camera = null;
|
||
this.pointerDownPos = Vector3.Zero();
|
||
this.pointerUpPos = Vector3.Zero();
|
||
}
|
||
|
||
/**
|
||
* 初始化
|
||
*/
|
||
init(): void {
|
||
this.scene = this.mainApp.appScene.object;
|
||
this.camera = this.mainApp.appCamera.object;
|
||
|
||
// 监听场景点击事件,点击空白处隐藏DOM
|
||
if (this.scene) {
|
||
this.scene.onPointerObservable.add((pointerInfo: PointerInfo) => {
|
||
const { type, event, pickInfo } = pointerInfo;
|
||
|
||
if (type === PointerEventTypes.POINTERDOWN) {
|
||
this.pointerDownPos.set(event.clientX, 0, event.clientY);
|
||
} else if (type === PointerEventTypes.POINTERUP) {
|
||
this.pointerUpPos.set(event.clientX, 0, event.clientY);
|
||
const distance = Vector3.Distance(this.pointerDownPos, this.pointerUpPos);
|
||
|
||
// 只有在没有移动的情况下才处理单击(距离小于5像素)
|
||
if (distance < 5) {
|
||
// 如果没有点击到任何物体,隐藏所有DOM
|
||
if (!pickInfo || !pickInfo.hit) {
|
||
this.hideAll();
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 添加DOM元素到3D坐标
|
||
* @param id 唯一标识符
|
||
* @param dom DOM元素
|
||
* @param position 3D坐标 [x, y, z]
|
||
* @param offset 2D偏移量 { x, y },可选
|
||
*/
|
||
attach(id: string, dom: HTMLElement, position: [number, number, number], offset?: { x: number; y: number }): void {
|
||
const vector3 = new Vector3(position[0], position[1], position[2]);
|
||
|
||
// 设置DOM样式
|
||
dom.style.position = 'absolute';
|
||
dom.style.pointerEvents = 'auto';
|
||
dom.style.zIndex = '1000';
|
||
dom.style.display = 'block';
|
||
|
||
// 存储DOM元素信息
|
||
this.domElements.set(id, {
|
||
dom,
|
||
position: vector3,
|
||
offset: offset || { x: 0, y: 0 },
|
||
visible: true
|
||
});
|
||
|
||
// 立即更新一次位置
|
||
this.updateSingleDomPosition(id);
|
||
}
|
||
|
||
/**
|
||
* 移除DOM元素
|
||
* @param id 唯一标识符
|
||
*/
|
||
detach(id: string): void {
|
||
const element = this.domElements.get(id);
|
||
if (element) {
|
||
element.dom.style.display = 'none';
|
||
this.domElements.delete(id);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新DOM元素的3D坐标
|
||
* @param id 唯一标识符
|
||
* @param position 新的3D坐标 [x, y, z]
|
||
*/
|
||
updatePosition(id: string, position: [number, number, number]): void {
|
||
const element = this.domElements.get(id);
|
||
if (element) {
|
||
element.position.set(position[0], position[1], position[2]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新所有DOM元素的位置
|
||
*/
|
||
updateDomPositions(): void {
|
||
this.domElements.forEach((_, id) => {
|
||
this.updateSingleDomPosition(id);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 更新单个DOM元素的位置
|
||
*/
|
||
private updateSingleDomPosition(id: string): void {
|
||
const element = this.domElements.get(id);
|
||
if (!element || !this.scene || !this.camera) return;
|
||
|
||
const { dom, position, offset, visible } = element;
|
||
|
||
// 如果标记为不可见,直接隐藏
|
||
if (!visible) {
|
||
dom.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
// 将3D坐标转换为2D屏幕坐标
|
||
const engine = this.scene.getEngine();
|
||
const width = engine.getRenderWidth();
|
||
const height = engine.getRenderHeight();
|
||
|
||
// 使用正确的矩阵:单位矩阵 + 变换矩阵
|
||
const worldMatrix = Matrix.Identity();
|
||
const transformMatrix = this.scene.getTransformMatrix();
|
||
const viewport = this.camera.viewport.toGlobal(width, height);
|
||
|
||
const screenPos = Vector3.Project(
|
||
position,
|
||
worldMatrix,
|
||
transformMatrix,
|
||
viewport
|
||
);
|
||
|
||
// 检查是否在相机视野内
|
||
if (screenPos.z < 0 || screenPos.z > 1) {
|
||
console.log('DOM 不在视野内,隐藏');
|
||
dom.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
// 应用偏移量并更新DOM位置
|
||
dom.style.display = 'block';
|
||
dom.style.left = `${screenPos.x + (offset?.x || 0)}px`;
|
||
dom.style.top = `${screenPos.y + (offset?.y || 0)}px`;
|
||
}
|
||
|
||
/**
|
||
* 清理所有DOM元素
|
||
*/
|
||
clean(): void {
|
||
this.domElements.forEach((element) => {
|
||
element.dom.style.display = 'none';
|
||
});
|
||
this.domElements.clear();
|
||
}
|
||
|
||
/**
|
||
* 获取所有已附加的DOM元素ID列表
|
||
*/
|
||
getAttachedIds(): string[] {
|
||
return Array.from(this.domElements.keys());
|
||
}
|
||
|
||
/**
|
||
* 隐藏所有DOM元素
|
||
*/
|
||
hideAll(): void {
|
||
console.log('hideAll 被调用,当前元素数量:', this.domElements.size);
|
||
this.domElements.forEach((element, id) => {
|
||
console.log('隐藏元素:', id, element.dom);
|
||
element.visible = false;
|
||
element.dom.style.display = 'none';
|
||
});
|
||
}
|
||
}
|