# Raycast (射线检测)

- **Summary**: Five 提供了增强版的射线检测机制，内置 BVH 加速，并扩展了 Three.js Raycaster 以支持更灵活的过滤和性能优化。
- **Schema**: 参见下方 `Three.js Raycaster Extensions`。
- **Concepts**: BVH, IntersectRaycaster, Raycaster Extensions。
- **Examples**: 提供基础检测与性能优化示例。

## Schema: Three.js Raycaster Extensions

> **Definition**: [Raycaster](../../five/declare.d.ts)

Five 对 Three.js 的 `Raycaster` 进行了扩展，在 `declare module 'three'` 中增加了以下属性：

```typescript
// defined in five/declare.d.ts
declare module 'three' {
  export interface Raycaster {
    /**
     * 只捕获第一个焦点 (性能优化)
     * - true: 找到第一个交点后立即停止检测，返回结果。
     * - false (默认): 检测所有可能的交点。
     */
    firstHitOnly?: boolean;

    /**
     * 自定义交点过滤器
     * - 回调函数，返回 boolean。
     * - true: 保留该交点。
     * - false: 丢弃该交点。
     */
    hitFilter?: (intersect: THREE.Intersection) => boolean;

    /**
     * 是否由近及远排序
     * - true (默认): 返回的结果数组按距离摄像机从近到远排序。
     * - false: 结果顺序不确定（通常按遍历顺序）。
     */
    sortByDistance?: boolean;

    /**
     * 作用楼层 (Five 特有逻辑)
     * - 用于多楼层场景过滤。
     */
    floorIndex?: number;
  }

  export interface Intersection {
    /** 命中的具体 Tile 对象 (仅 3DTiles) */
    tile?: Tile;
    /** 命中的 ViewLayer 对象 */
    viewLayer?: ViewLayer;
    /** 命中的 Model 对象 */
    model?: Model;
  }
}
```

## Concepts

### 1. Five 内置 BVH (Bounding Volume Hierarchy)
Five 内置了一套高效的 BVH 实现，用于优化 `Mesh` 和 `Points` 的射线检测。
*   **作用**: 在进行射线检测时，不直接遍历所有三角形或点，而是先与构建好的 BVH 树（包围盒层次结构）进行相交测试。只有当射线穿过包围盒时，才进一步检测内部的几何体。
*   **优势**: 极大提升了复杂模型（如数十万面的 Mesh 或百万级 Points）的碰撞检测性能。
*   **透明性**: 开发者通常无需手动操作 BVH，Five 在加载模型时会自动构建，并在调用 `intersectRaycaster` 时自动应用。

### 2. models.intersectRaycaster / model.intersectRaycaster vs 原生 intersectObject

Five 提供了 `model.intersectRaycaster` 方法，与 Three.js 原生的 `raycaster.intersectObject` 有显著区别：

| 特性 | `five.model.intersectRaycaster` | `raycaster.intersectObject` (Three.js 原生) |
| :--- | :--- | :--- |
| **检测范围** | 聚合检测 `five.model.shownModels` 中所有可见模型。 | 仅检测指定的单个 Object3D 及其子节点。 |
| **结果排序** | 自动合并所有模型的检测结果，并根据 `raycaster.sortByDistance` 排序。 | 仅对当前 Object 及其子节点的结果排序。 |
| **扩展支持** | **支持** `firstHitOnly`, `hitFilter`, `sortByDistance` 等扩展属性。 | 不支持 Five 的扩展属性（需手动实现）。 |
| **内部优化** | 自动调用模型内部的优化逻辑（如 BVH, Tile3D 的空间索引）。 | 仅进行标准的场景图遍历检测。 |
| **使用场景** | **推荐**。适用于全局点击、地板检测、物体拾取等业务场景。 | 仅在需要检测特定原生 Object3D 时使用。 |

## Examples

### 基础用法：检测点击位置

最常见的场景是将鼠标点击的屏幕坐标转换为 3D 空间中的点。

```typescript
import * as THREE from 'three';
import { Five } from '@realsee/five';

const five = new Five();
// ... 初始化 ...

const raycaster = new THREE.Raycaster();
const vector = new THREE.Vector2();

// 假设 onTap 是点击事件处理函数
function onTap(event: { x: number, y: number }) {
  // 1. 将屏幕坐标转换为归一化设备坐标 (NDC)
  // 注意：five.getElement() 获取 canvas 容器
  const element = five.getElement();
  if (!element) return;

  const rect = element.getBoundingClientRect();
  vector.x = ((event.x - rect.left) / rect.width) * 2 - 1;
  vector.y = -((event.y - rect.top) / rect.height) * 2 + 1;

  // 2. 设置射线
  raycaster.setFromCamera(vector, five.camera);

  // 3. 调用 Five 的检测方法
  const intersects = five.model.intersectRaycaster(raycaster);

  if (intersects.length > 0) {
    const firstHit = intersects[0];
    console.log('点击到了模型:', firstHit.object);
    console.log('交点坐标 (世界坐标):', firstHit.point);
  }
}
```

### 性能优化：只获取第一个交点

在进行如“地面检测”或“视线遮挡检测”时，通常只需要知道最近的交点。此时开启 `firstHitOnly` 可以显著提升性能。

```typescript
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouseVector, five.camera);

// 开启性能优化：找到最近的一个交点后立即停止
raycaster.firstHitOnly = true;

// 结果数组长度最多为 1
const intersects = five.model.intersectRaycaster(raycaster);

if (intersects.length > 0) {
    // 处理最近的交点
}
```

### 高级用法：使用过滤器

如果你只想检测特定类型的物体（例如只检测地板，忽略天花板），可以使用 `hitFilter`。

```typescript
raycaster.hitFilter = (intersect) => {
  // 示例：假设地板的法线是向上的 (0, 1, 0)
  // 只保留法线朝上的交点（地板）
  if (intersect.face && intersect.face.normal.y > 0.5) {
    return true; // 保留
  }
  return false; // 丢弃
};

const intersects = five.model.intersectRaycaster(raycaster);
```

## Debugging

*   **验证交点**: 在开发阶段，可以在交点处添加一个临时的红色小球 `THREE.Mesh`，直观地确认检测位置是否正确。
*   **检查模型可见性**: `intersectRaycaster` 只会检测 `visible = true` 的模型。如果检测不到，请检查模型的可见性状态。
*   **Performance**: 如果射线检测导致掉帧，检查是否频繁调用了全场景检测。尝试使用 `firstHitOnly` 或减小检测频率（如节流）。

## Common Pitfalls

1.  **混淆 API**: 错误地使用 `raycaster.intersectObject(five.scene)`。虽然这也能工作，但会失去 Five 的 BVH 优化和多模型聚合能力，导致性能下降或漏检。请始终优先使用 `five.model.intersectRaycaster(raycaster)`。
2.  **忽略坐标系**: `raycaster.setFromCamera` 需要 NDC 坐标（-1 到 1），而不是像素坐标。务必进行正确的坐标转换。
3.  **Tile3D 模型的特殊性**: 对于大型 Tile3D 场景，射线检测可能涉及动态加载的瓦片。Five 内部已处理这些逻辑，外部调用方式不变，但返回的 `object` 可能是某个具体的 Tile 节点。

## Related

*   [Parameter](./parameter.md): 了解模型参数配置。
*   [Screen Project](./screen-project.md): 屏幕坐标与空间坐标转换（标签、拾取的前置步骤）。
*   [Event](./event.md): 手势事件系统，获取点击坐标的来源。
*   [Coordinate System](./coordinate-system.md): 坐标系定义，理解 NDC 与世界坐标。

---

```yaml
tags: [点击, 拾取, 碰撞检测, 物体选择, raycast, bvh, interaction, performance, picking, hit-test, click, floor-detection]
```
