# Interactions API

`InteractionManager` 提供了一种统一的方式来处理 3D 场景中的用户输入（鼠标、触摸、指针）。它将屏幕空间的射线检测转换为世界空间坐标，并将对应事件直接分发到被命中的 3D 对象上。

`InteractionManager` 在 `Viewer` 内部自动实例化，可通过 `viewer.interactionManager` 访问。

对于三角形较多的静态模型，可以在模型加载后调用 `enableMeshBVHRaycast(root)`。该函数不会修改 Three.js 原型；它先保留每个 Mesh 的局部包围体粗筛，只有射线命中包围球后才为至少 10000 个三角形的 geometry 按需建立并复用 `three-mesh-bvh`。小几何、蒙皮、morph target 或自定义 raycast 对象会继续使用原始路径。

```ts
import { enableMeshBVHRaycast } from 'u-space';

enableMeshBVHRaycast(loadedModel);
```

## 启用交互事件

出于性能考虑，指针移动事件默认处于禁用状态。如需悬停和拖拽效果，必须显式开启。

```typescript
// 启用鼠标移动事件（如 pointerenter 和 pointerleave）
viewer.interactionManager.pointerMoveEventsEnabled = true;
```

### 目标对象

可以限制射线检测所针对的对象范围。默认值为 `scene.children`（即整个场景的直接子级，含递归嵌套）。
`BaseMesh` / `BaseGroup` 默认会在自身 `visible === false` 时让 `raycast()` 返回 `false`，从而阻止自身和子级继续参与递归射线检测；如需隐藏但仍可交互，可将对象的 `ignoreInvisibleWhenRaycast` 设为 `false`。`setInteractable(object, false)` / `addIgnore(object)` 会在命中后过滤该对象及其子对象。

```typescript
// 仅检测指定对象
viewer.interactionManager.targetObjects = [myBox, myModel];

// 恢复默认：检测整个场景
viewer.interactionManager.targetObjects = viewer.scene.children;
```

## 添加事件监听

由于 `u-space` 对象（以及继承自 `BaseMesh`、`BaseGroup` 的对象）支持自定义事件，你可以像操作 DOM 元素一样原生地绑定监听器。

```typescript
const myBox = new Mesh(geometry, material);
viewer.scene.add(myBox);

// 直接在 Mesh 或 Model 上绑定监听器
myBox.addEventListener('click', (eventData) => {
  // 底层指针事件和射线检测数据
  const intersect = eventData.event.intersect;

  console.log('点击位置：', intersect.point);
});
```

## 支持的事件类型

以下事件类型可在可交互对象上监听：

- `click`：指针点击对象时触发。内置智能过滤：长按（>500ms）或移动距离过大时忽略，双击时不重复触发。
- `dblclick`：快速双击对象时触发。
- `rightclick`：右键短按松开时触发（长按或拖拽不触发；浏览器原生上下文菜单已被阻止）。在 `pointerup` 时派发，因此可以正确过滤相机拖拽等操作。
- `pointerdown`：指针按键在对象上按下时触发。
- `pointerup`：指针按键在对象上释放时触发。
- `pointermove`：指针在对象上移动时触发，需要 `pointerMoveEventsEnabled = true`。
- `pointerenter`：光标进入对象范围时触发，需要 `pointerMoveEventsEnabled = true`。
- `pointerleave`：光标离开对象范围时触发，需要 `pointerMoveEventsEnabled = true`。

### 事件冒泡

事件会沿父级链向上冒泡。若要阻止事件继续传播，可调用事件载荷上的 `stopPropagation()`：

```typescript
myObject.addEventListener('click', (e) => {
  e.event.stopPropagation();
});
```

## `InteractionEvent`

传递给所有监听回调的事件对象，位于 `event` 键下。

### 属性

| 属性            | 类型                                       | 说明                                                                        |
| :-------------- | :----------------------------------------- | :-------------------------------------------------------------------------- |
| `type`          | `InteractionEventType`                     | 事件类型字符串（如 `'click'`）。                                            |
| `target`        | `Object3D`                                 | 触发事件的原始 3D 对象（第一个命中点）。                                    |
| `currentTarget` | `Object3D`                                 | 冒泡链中当前处理事件的对象。                                                |
| `intersect`     | `Intersection \| null`                     | Three.js 射线检测数据：`point`、`face`、`distance`、`uv` 等。               |
| `originalEvent` | `PointerEvent \| MouseEvent`               | 原始 DOM 指针/鼠标事件。                                                    |

### 方法

#### `stopPropagation()`

阻止事件继续向上冒泡。

## `InteractionManager` API

### 属性

| 属性                       | 类型                          | 默认值            | 说明                                                       |
| :------------------------- | :---------------------------- | :---------------- | :--------------------------------------------------------- |
| `targetObjects`            | `Object3D[]`                  | `scene.children`  | 射线检测的目标对象列表，默认为场景的直接子级。             |
| `pointerMoveEventsEnabled` | `boolean`                     | `false`           | 启用 `pointermove`、`pointerenter`、`pointerleave` 事件。  |

### 方法

#### `enable()` / `disable()`

全局启用或禁用所有交互事件。禁用后不再触发任何交互事件。

```typescript
viewer.interactionManager.disable(); // 暂停交互
viewer.interactionManager.enable();  // 恢复交互
```

#### `setInteractable(object, interactable)`

控制单个对象是否可交互。设为 `false` 后该对象及其所有子对象不再参与射线检测。

```typescript
viewer.interactionManager.setInteractable(myModel, false); // 禁止交互
viewer.interactionManager.setInteractable(myModel, true);  // 恢复交互
```

#### `addIgnore(object)` / `removeIgnore(object)`

`setInteractable` 的别名。`addIgnore(obj)` 等同于 `setInteractable(obj, false)`，`removeIgnore(obj)` 等同于 `setInteractable(obj, true)`。

```typescript
viewer.interactionManager.addIgnore(helperObject);    // 忽略辅助对象
viewer.interactionManager.removeIgnore(helperObject);  // 恢复
```

#### `setCamera(camera)`

更新用于射线检测的相机。由 `viewer.setCamera()` 自动调用。

#### `dispose()`

移除所有 DOM 事件监听，清理内部状态。

```typescript
viewer.interactionManager.dispose();
```

---

## Selection

`Selection` 提供对象选择能力，包括单选、多选、toggle 和框选（rubber-band）。

```typescript
import { Selection } from 'u-space';

const selection = new Selection(viewer.renderer.domElement, viewer.scene, viewer.camera);
```

### 基础选择

```typescript
selection.select(myObject);           // 选中
selection.select([obj1, obj2]);       // 多选
selection.deselect(myObject);         // 取消选择
selection.toggle(myObject);           // 切换选择状态
selection.clear();                    // 清空选择
selection.isSelected(myObject);       // 检查是否已选
```

### 属性

| 属性            | 类型            | 说明                                |
| :-------------- | :-------------- | :---------------------------------- |
| `selected`      | `Set<Object3D>` | 当前已选对象集合。                  |
| `selectedArray` | `Object3D[]`    | 当前已选对象数组。                  |
| `targetObjects` | `Object3D[]`    | 框选时的候选对象（默认为场景子级）。 |

### 框选

启用后可用鼠标拖动矩形区域选择多个对象。框选覆盖层会渲染在容器元素上。

```typescript
selection.enableBoxSelection({ deep: true });   // 开启框选
selection.disableBoxSelection();                 // 关闭框选
```

#### `BoxSelectionOptions`

| 属性        | 类型      | 默认值 | 说明                            |
| :---------- | :-------- | :----- | :------------------------------ |
| `className` | `string`  | —      | 框选矩形的 CSS 类名。          |
| `deep`      | `boolean` | `true` | 是否递归检测子对象。            |

### 事件

```typescript
selection.addEventListener('select', (e) => {
  console.log('新选中:', e.objects);
});

selection.addEventListener('deselect', (e) => {
  console.log('取消选中:', e.objects);
});

selection.addEventListener('change', (e) => {
  console.log('当前选中:', e.selected.size, '个对象');
});
```

### 方法

#### `setCamera(camera)`

更新用于框选投影计算的相机。切换相机时需调用。

#### `dispose()`

关闭框选、移除事件监听、清空选择集。
