# 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 对 Three.js 的 `Raycaster` 进行了扩展，在 `declare module 'three'` 中增加了以下属性：

    // 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 空间中的点。

    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` 可以显著提升性能。

    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`。

    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: 了解模型参数配置。
- Screen Project: 屏幕坐标与空间坐标转换（标签、拾取的前置步骤）。
- Event: 手势事件系统，获取点击坐标的来源。
- Coordinate System: 坐标系定义，理解 NDC 与世界坐标。

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