five.project2d 接口定义。Definition: Five
// defined in Five class
interface Five {
/**
* 计算三维坐标对应到屏幕的二维坐标
* @param vector - 三维空间坐标 (World Space)
* @param testModel - 是否进行模型遮挡检测 (默认 false)。如果为 true,且该点被模型遮挡,则返回 null。
* @returns 二维屏幕坐标 (相对于 Canvas 左上角) 或 null (如果点在视野外或被遮挡)
*/
project2d(vector: THREE.Vector3, testModel?: boolean): THREE.Vector2 | null;
}
在 Five (Three.js) 中,坐标转换通常遵循以下流程:
| 方向 | 方法 | 核心逻辑 | 用途 |
|---|---|---|---|
| 3D ➔ 2D | five.project2d(vector) |
World ➔ NDC ➔ Screen | 标签 (Tag)、HUD、跟随 3D 物体的 UI |
| 2D ➔ 3D | Raycaster |
Screen ➔ NDC ➔ Ray | 点击拾取 (Picking)、地面检测、放置物体 |
five.project2d 是 Five 提供的便捷方法,它封装了以下逻辑:
最典型的应用场景是在 3D 场景中的某个物体上方显示一个 HTML 标签。
import { Five, Mode } from "@realsee/five";
import * as THREE from "three";
const five = new Five();
// ... 初始化 five ...
// 1. 创建标签 DOM 元素
const label = document.createElement("div");
label.textContent = "Living Room";
label.style.position = "absolute";
label.style.padding = "4px 8px";
label.style.background = "rgba(0, 0, 0, 0.6)";
label.style.color = "white";
label.style.borderRadius = "4px";
label.style.pointerEvents = "none"; // 避免阻挡鼠标事件
label.style.transform = "translate(-50%, -100%)"; // 居中并显示在点上方
label.style.display = "none"; // 默认隐藏
document.body.appendChild(label); // 添加到 container (注意 container 的定位 context)
// 2. 定义目标空间坐标 (例如客厅中心)
const targetPosition = new THREE.Vector3(1.5, 2.0, -3.0);
// 3. 在每一帧渲染时更新标签位置
five.on("render.prepare", () => {
// 计算屏幕坐标
// 参数2: false 表示不检测遮挡 (性能更好);true 表示如果被墙挡住则隐藏
const screenPos = five.project2d(targetPosition, true);
if (screenPos === null) {
// 如果返回 null,说明点在相机背面,或者被遮挡(如果开启了 testModel)
label.style.display = "none";
} else {
label.style.display = "block";
// 更新 DOM 位置
// 注意:screenPos 是相对于 Canvas 的坐标。
// 如果 Canvas 占满全屏,直接使用即可。
// 如果 Canvas 有偏移,需加上 Canvas 的 offset。
label.style.left = `${screenPos.x}px`;
label.style.top = `${screenPos.y}px`;
}
});
将鼠标点击的屏幕坐标转换为 3D 射线,用于检测物体。
注意: 更多射线检测细节请参考 Raycast。
import * as THREE from "three";
// 假设 container 是 five 的容器元素
const container = five.getElement()!.parentElement!;
container.addEventListener("click", (event) => {
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// 1. 计算 NDC 坐标 (x, y 范围 [-1, 1])
// event.clientX/Y 是相对于视口的坐标
// getBoundingClientRect 用于获取容器的位置
const rect = container.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; // 注意 Y 轴翻转
// 2. 从相机发射射线
raycaster.setFromCamera(mouse, five.camera);
// 3. 进行相交检测
const intersects = five.model.intersectRaycaster(raycaster);
if (intersects.length > 0) {
console.log("Hit point:", intersects[0].point);
}
});
坐标系原点:
project2d 返回的是 DOM 风格 的坐标 (左上角原点),可以直接赋给 CSS left/top。性能开销:
project2d(pos, true) 开启 testModel 会进行射线检测,性能开销较大。如果在 render.prepare 中对大量点进行检测,可能会导致掉帧。false;或者仅对可视范围内的关键标签进行遮挡检测;或者限制检测频率(如每 10 帧检测一次)。Canvas 尺寸:
project2d 内部使用 five.renderer.getSize() 和 five.viewport 计算。确保 Canvas 尺寸与 DOM 显示尺寸一致 (通常 Five 会自动处理,但如果使用了 CSS 缩放需注意)。Null 返回值:
project2d 的返回值是否为 null。点在相机背面、超出视锥体裁剪范围、或被遮挡(开启 testModel 时)都会返回 null。tags: [标签, 坐标转换, 信息面板, coordinate, screen, project, label, ui, tag, HUD, 3D转2D, overlay, tooltip]