Files
zhengte.babylonjs-sdk/src/babylonjs/AppDomTo3D.ts
2026-04-24 19:17:31 +08:00

199 lines
6.0 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 { 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';
});
}
}