# 模型裁切 (Model Clipper)

- **Summary**: 使用 `parameter.clippers` 定义裁切盒，用于隐藏 (discard) 模型中特定区域的点云或网格。
- **Schema**: `PBMClipperParameter` 数组，包含变换矩阵和楼层索引。
- **Concepts**: Clipping Box (裁切盒), Matrix Transformation (矩阵变换), Negative Space (负空间).
- **Configuration**: `five.model.clippers`.
- **Examples**: 创建裁切盒并应用。

## Schema

> **Definition**: [PBMClipperParameter](../../five/model/materials/pbmMaterial.d.ts)

```typescript
/** 模型裁切参数 */
interface PBMClipperParameter {
  /**
   * 裁切盒的变换矩阵。
   * @description
   * 定义了一个变换后的立方体空间。
   * 基础形状为边长为 1 的立方体，中心在 (0,0,0)，范围 [-0.5, 0.5]。
   * 凡是落在这个变换后立方体内部的模型点，都会被丢弃 (discard)。
   */
  clippingBoxMatrix: THREE.Matrix4;

  /**
   * (可选) 楼层索引。
   * @description
   * 如果指定，则裁切仅对该楼层的点云生效。
   * 如果未指定 (undefined)，则对所有楼层生效。
   */
  floorIndex?: number;
}
```

## Concepts

### Clipping Box (裁切盒)
Five 的裁切机制是基于**负空间**的：
- 默认情况下，所有模型点都是可见的。
- 当你添加一个 Clipper 时，你实际上是在定义一个“不可见区域”。
- 任何位于 Clipper 定义的 3D 盒体内部的点，都不会被渲染。

### Matrix Transformation (矩阵变换)
Clipper 的几何定义依赖于 `clippingBoxMatrix`。
- shader 内部逻辑是将世界坐标系下的点，乘以 `clippingBoxMatrix` 的**逆矩阵**。
- 然后判断该点是否在 `[-0.5, 0.5]` 的单位立方体范围内。
- 这意味着你可以直接使用 `THREE.Mesh` (BoxGeometry 1x1x1) 的 `matrix` 属性作为 `clippingBoxMatrix`，所见即所得。

## Configuration

你可以通过 `five.model.clippers` 属性来读写裁切配置。

| 属性 | 类型 | 说明 |
| --- | --- | --- |
| `five.model.clippers` | `PBMClipperParameter[]` | 设置裁切参数数组。设置新数组会覆盖旧配置。 |

## Examples

### 基础用法 (Basic Usage)

创建一个不可见的 Box，将其位置和大小作为裁切区域。

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

// 1. 创建一个辅助 Mesh 来定义裁切区域
// 注意：必须使用 1x1x1 的 BoxGeometry，因为 Shader 是基于单位立方体判断的
const clipperMesh = new THREE.Mesh(
  new THREE.BoxGeometry(1, 1, 1),
  new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true })
);

// 2. 设置裁切盒的位置、旋转和缩放
clipperMesh.position.set(0, 1.5, 0); // 抬高位置
clipperMesh.scale.set(2, 3, 2);      // 设定大小为 2x3x2
clipperMesh.rotation.y = Math.PI / 4; // 旋转 45 度

// 更新矩阵，确保 .matrix 属性是最新的
clipperMesh.updateMatrix();

// (可选) 将辅助 Mesh 加入场景以便调试看到范围
five.scene.add(clipperMesh);

// 3. 应用裁切参数
five.model.clippers = [
  {
    clippingBoxMatrix: clipperMesh.matrix,
    // floorIndex: 0 // 可选：仅裁切第 0 层
  }
];

// 4. 触发重绘
five.needsRender = true;
```

### 动态更新 (Dynamic Update)

如果需要动态移动裁切盒，需要重新赋值 `five.model.clippers` 或手动触发更新机制（虽然 setter 会自动触发更新，但如果只修改数组内部对象可能需要重新赋值）。

```typescript
function updateClipper(mesh: THREE.Mesh) {
  mesh.updateMatrix();

  // 重新赋值以触发 Five 内部的 setter 和 uniform 更新
  five.model.clippers = [{
    clippingBoxMatrix: mesh.matrix
  }];
}

// 在动画循环或交互事件中调用
// ...
```

## Debugging

- **辅助显示**: 始终建议在开发阶段将用于生成矩阵的 `THREE.Mesh` (Wireframe 模式) 添加到 `five.scene` 中，这样可以直观地看到裁切范围是否正确。
- **单位问题**: 确保 `BoxGeometry` 构造时没有传入参数 (默认 1,1,1) 或者显式传入 `(1, 1, 1)`。如果使用了 `(10, 10, 10)` 的几何体，那么 `scale` 设置为 1 时实际裁切范围是 10，可能会导致误解。建议统一使用单位几何体 + `scale` 的方式。

## Common Pitfalls

1.  **矩阵未更新**: 修改了 Mesh 的 position/scale 后忘记调用 `updateMatrix()`，导致裁切位置没变。
2.  **几何体尺寸**: 使用了非 `1x1x1` 的 BoxGeometry，导致裁切范围与 Mesh视觉大小不一致（Shader 始终假定是单位立方体）。
3.  **楼层索引**: 设置了错误的 `floorIndex` 导致裁切看起来没生效（因为该区域的点可能属于另一个 floorIndex）。
4.  **坐标系**: `clippingBoxMatrix` 是世界坐标系下的变换。如果模型本身有额外的父级变换（通常 Five 的模型直接在 Scene 下，或者是 Identity 变换），通常不需要额外处理。

## Related

*   [Model](./model.md): 模型模块概览。

---

```yaml
tags: [裁切, 剖面, 隐藏部分模型, 开盖, 房屋剖面, clipper, model, cut, discard, geometry, clipping-box, section-view]
```
