npm package: @ridp/threejs
Description: 基于 Three.js 的 3D 可视化工具库,提供场景管理、模型加载、性能优化等开箱即用的功能。
Latest version: 1.5.2

# @ridp/threejs

基于 Three.js 的 3D 可视化工具库,提供场景管理、模型加载、性能优化等开箱即用的功能。

> **📢 重要提示**:
> - ⚠️ `useThreeJs()` Hook 已弃用,请使用 **ThreeIns 类** (功能更强大,不限框架)
> - ⚠️ `frameArea()` 方法已弃用,请使用 **setView() 方法** (支持多视角切换)
>
> **快速迁移**:
> ```javascript
> // ❌ 旧用法 (已弃用)
> const { scene, camera } = useThreeJs('#container', opts);
> threeIns.frameArea(model, 1.0);
>
> // ✅ 新用法 (推荐)
> const threeIns = new ThreeIns('#container', opts);
> threeIns.setView(model, ViewType.ISO, { scale: 1.0 });
> ```

## 特性

- 🚀 **开箱即用**: 零配置快速搭建 Three.js 场景
- 📦 **智能缓存**: 基于 IndexedDB 的模型缓存机制,大幅提升二次加载速度
- 🎯 **视角控制**: 内置多种视角(俯视、侧视、等轴测)和平滑过渡动画
- 🔧 **性能优化**: 模型优化、批量加载、渐进式渲染等性能优化工具
- 🎨 **辅助工具**: 包围盒检测、射线拾取、碰撞检测等实用工具
- 💡 **TypeScript 支持**: 完整的类型定义和 JSDoc 文档
- 🌐 **框架无关**: 可在任何 JavaScript/TypeScript 项目中使用(Vue/React/Angular/原生JS)

## 安装

### 依赖要求

本库依赖以下 peer dependencies:

```json
{
  "peerDependencies": {
    "dexie": ">=4.0.0 <5.0.0",
    "vue": ">=3.0.0 <4.0.0",
    "three": ">=0.178.0 <1.0.0"
  }
}
```

### NPM 安装

```bash
npm install @ridp/threejs dexie@^4 vue@^3 three@^0.178
```

### Yarn 安装

```bash
yarn add @ridp/threejs dexie@^4 vue@^3 three@^0.178
```

### PNPM 安装

```bash
pnpm add @ridp/threejs dexie@^4 vue@^3 three@^0.178
```

### vite.config.js 配置
因为涉及 worker，需要添加以下配置项：
```javascript
import { defineConfig } from 'vite';

export default defineConfig(() => {
  return {
    // plugins,
    // build: { ... }
    optimizeDeps: {
      // ...
      exclude: ['@ridp/threejs']
    },
  }
})

```

## 快速开始

### 基础场景初始化

```javascript
import { ThreeIns } from '@ridp/threejs';

// 创建 3D 场景实例
const threeJsIns = new ThreeIns('#container', {
  css3d: true,           // 启用 CSS3D 渲染器
  renderType: 'loop',     // 渲染循环类型
  initListener: true,     // 初始化事件监听
  initialFov: 75,         // 初始 FOV
  control: {              // OrbitControls 配置
    init: true,
    options: {
      maxDistance: Infinity,
      minDistance: 0,
      enablePan: true,
      enableDamping: true,
      dampingFactor: 0.25,
    },
  },
});

// 访问核心对象
console.log(threeJsIns.scene);    // THREE.Scene
console.log(threeJsIns.camera);   // THREE.PerspectiveCamera
console.log(threeJsIns.renderer); // THREE.WebGLRenderer
console.log(threeJsIns.control);  // THREE.OrbitControls
```

### 加载 GLTF 模型

```javascript
import { useGLTFLoader, initEnvImage } from '@ridp/threejs';

const { asyncFetch, logCacheReport } = useGLTFLoader();

// 加载模型(带缓存)
async function loadModel() {
  const modelUrl = '/models/car.glb';
  const version = '1.0.0';

  const [err, model] = await asyncFetch(modelUrl, version);

  if (model) {
    threeJsIns.scene.add(model);
    threeJsIns.setView(model, ViewType.ISO, {
      scale: 1.0,
      showBox: true,
      animate: true,
      duration: 1000
    });
  }

  // 查看缓存性能报告
  logCacheReport();
}

// 加载环境贴图
await initEnvImage(threeJsIns.scene, '/hdr/studio.exr');
loadModel();
```

## 核心 API

### ThreeIns 类

主要的 3D 场景管理类。

#### 构造函数

```javascript
new ThreeIns(selector: string, options: ThreeInsOptions)
```

**参数:**
- `selector`: DOM 元素选择器(如 `#canvas-container`)
- `options`: 配置对象
  - `css3d`: 是否启用 CSS3D 渲染器(默认: `false`)
  - `renderType`: 渲染循环类型,`'loop'` 或 `'ondemand'`(默认: `'loop'`)
  - `initListener`: 是否自动监听窗口大小变化(默认: `true`)
  - `initialFov`: 初始垂直 FOV(默认: `50`)
  - `control`: OrbitControls 配置
    - `init`: 是否初始化控制器(默认: `true`)
    - `options`: OrbitControls 配置对象

#### 实例属性

- `scene: THREE.Scene` - 场景对象
- `camera: THREE.PerspectiveCamera` - 相机对象
- `renderer: THREE.WebGLRenderer` - WebGL 渲染器
- `css3DRenderer: THREE.CSS3DRenderer` - CSS3D 渲染器(如果启用)
- `control: THREE.OrbitControls` - 轨道控制器
- `domElement: HTMLElement` - 容器 DOM 元素

#### 主要方法

##### setView()

设置模型视角。

```javascript
threeJsIns.setView(
  model: Object3D,
  viewType: ViewType,
  options?: {
    scale?: number,        // 缩放比例,1=占满画布,0.5=50%,2=0%(默认: 1)
    showBox?: boolean,     // 是否显示包围盒(默认: false)
    animate?: boolean,     // 是否启用动画(默认: false)
    duration?: number,     // 动画时长(毫秒,默认: 1000)
    offset?: Vector3       // 中心点偏移
  }
): void
```

**视角类型 (ViewType):**
- `ViewType.TOP` - 俯视(从上往下)
- `ViewType.RIGHT` - 右视图(从右往左)
- `ViewType.LEFT` - 左侧视(从左往右)
- `ViewType.ISO` - 等轴测视角(对角线上方俯视)

**示例:**
```javascript
// 设置等轴测视角,模型占满画布
threeJsIns.setView(model, ViewType.ISO, {
  scale: 1.0,
  showBox: true,
  animate: true,
  duration: 800
});

// 设置俯视,模型缩小到 50%
threeJsIns.setView(model, ViewType.TOP, {
  scale: 0.5,
  animate: true
});
```

##### frameArea()

> **⚠️ 已弃用** - 此方法仅为兼容旧版本保留，请使用 `setView()` 方法代替。

自动调整相机位置使模型完整显示在视口中（默认使用等轴测视角）。

```javascript
threeJsIns.frameArea(
  model: Object3D,
  scale: number = 1,
  options?: {
    showBox?: boolean,    // 是否显示包围盒
    offset?: Vector3      // 中心点偏移
  }
): void
```

**参数说明:**
- `scale`: 显示比例
  - `1.0` - 模型占满画布
  - `0.5` - 模型占画布 50%
  - `2.0` - 模型放大到 200%

**示例:**
```javascript
// ❌ 旧用法（已弃用）
threeJsIns.frameArea(model, 1.0);

// ✅ 推荐用法 - 使用 setView
threeJsIns.setView(model, ViewType.ISO, { scale: 1.0 });

// ❌ 旧用法（已弃用）
threeJsIns.frameArea(model, 0.5, { showBox: true });

// ✅ 推荐用法 - 使用 setView
threeJsIns.setView(model, ViewType.ISO, {
  scale: 0.5,
  showBox: true,
  animate: true
});
```

**迁移指南:**
| 旧用法 (frameArea)      | 新用法 (setView)                               |
| ----------------------- | ---------------------------------------------- |
| `frameArea(model, 1.0)` | `setView(model, ViewType.ISO, { scale: 1.0 })` |
| `frameArea(model, 0.5)` | `setView(model, ViewType.ISO, { scale: 0.5 })` |
| `frameArea(model, 2.0)` | `setView(model, ViewType.ISO, { scale: 2.0 })` |

##### updateCameraFOV()

根据画布宽高比动态调整相机 FOV。

```javascript
threeJsIns.updateCameraFOV(): void
```

当窗口大小变化时自动调用,确保模型在不同宽高比下正确显示。

##### addAnimate()

添加自定义动画函数到渲染循环。

```javascript
threeJsIns.addAnimate(fn: Function): void
```

**示例:**
```javascript
let time = 0;
threeJsIns.addAnimate(() => {
  time += 0.01;
  model.rotation.y = time;
});
```

##### dispose()

销毁场景,释放所有资源。

```javascript
threeJsIns.dispose(): void
```

**清理内容:**
- 停止渲染循环
- 移除事件监听器
- 释放几何体、材质、纹理
- 清理 Stats 性能监控
- 销毁 OrbitControls

---

## Hooks

### useThreeJs()

> **⚠️ 已弃用** - 此 Hook 仅为兼容旧版本保留，请使用 **ThreeIns 类** 代替。

Vue 3 组合式 API 风格的 Three.js 场景初始化 Hook。

**为什么要弃用?**
- ❌ 功能被 ThreeIns 类完全覆盖
- ❌ 缺少视角切换功能 (setView)
- ❌ 性能优化不如 ThreeIns 类
- ❌ 仅限 Vue 项目使用
- ❌ 资源清理不够完善

**推荐迁移到 ThreeIns 类:**

```javascript
// ❌ 旧用法 (useThreeJs Hook - 已弃用)
import { useThreeJs } from '@ridp/threejs';
const { scene, camera, renderer, control } = useThreeJs('#container', { ... });

// ✅ 推荐用法 (ThreeIns 类)
import { ThreeIns, ViewType } from '@ridp/threejs';
const threeJsIns = new ThreeIns('#container', {
  css3d: true,
  renderType: 'loop',
  initListener: true,
  initialFov: 75,
  control: { init: true, options: { ... } }
});

// 访问核心对象
console.log(threeJsIns.scene);    // THREE.Scene
console.log(threeJsIns.camera);   // THREE.PerspectiveCamera
console.log(threeJsIns.renderer); // THREE.WebGLRenderer
console.log(threeJsIns.control);  // THREE.OrbitControls

// 使用强大的 setView 方法
threeJsIns.setView(model, ViewType.ISO, { scale: 1.0, animate: true });
```

**迁移对照表:**

| 功能        | useThreeJs (已弃用)                  | ThreeIns 类 (推荐)               |
| ----------- | ------------------------------------ | -------------------------------- |
| 初始化场景  | `useThreeJs('#el', opts)`            | `new ThreeIns('#el', opts)`      |
| 访问 scene  | `const { scene } = useThreeJs(...)`  | `threeJsIns.scene`               |
| 访问 camera | `const { camera } = useThreeJs(...)` | `threeJsIns.camera`              |
| 添加动画    | `addAnimate(fn)`                     | `threeJsIns.addAnimate(fn)`      |
| 视角切换    | ❌ 不支持                             | ✅ `setView(model, ViewType.ISO)` |
| 资源清理    | `dispose()`                          | ✅ `threeJsIns.dispose()`         |
| 适用范围    | 仅 Vue 3                             | 所有 JS/TS 项目                  |

---

### useGLTFLoader()

GLTF 模型加载器,支持 IndexedDB 缓存、内存缓存、重试机制和调试模式。

```javascript
const loader = useGLTFLoader({
  debug: false  // 是否开启调试日志(默认: false)
});

const {
  // 核心加载方法
  load,              // 基础加载方法
  asyncLoad,         // Promise 异步加载
  asyncFetch,        // 带缓存和重试的异步加载(推荐)
  asyncCacheLoad,    // [已弃用] 旧版缓存加载

  // 批量加载
  loadBatch,         // 批量加载模型
  loadBatchSequential, // 顺序批量加载

  // IndexedDB 缓存管理
  clearCache,        // 清空 IndexedDB 缓存
  invalidateCache,   // 使缓存失效
  getCacheStats,     // 获取缓存统计
  logCacheReport,    // 输出缓存性能报告
  getCache,          // 获取 IDBCache 实例
  resetCacheStats,   // 重置缓存统计

  // 内存缓存管理 (v1.4.2+)
  clearMemoryCache,      // 清空内存缓存
  getMemoryCacheInfo,    // 获取内存缓存统计
  deleteMemoryCache,     // 删除指定模型的内存缓存

  // 性能监控
  cacheMonitor       // 缓存监控器实例
} = loader;
```

**新增功能 (v1.4.2):**

1. **内存缓存机制**: 自动缓存已解析的 3D 模型对象，避免重复解析
2. **调试模式控制**: 通过初始化参数统一控制所有日志输出

#### 初始化配置

```javascript
// 生产环境 - 默认静默
const loader = useGLTFLoader();

// 开发环境 - 开启调试日志
const loader = useGLTFLoader({
  debug: true
});

// 所有操作都会输出详细日志
const model = await loader.asyncFetch('/models/car.glb', '1.0.0');
// 输出: [ asyncFetch ] ====> 缓存未命中
//       [ fetchArrayBuffer ] 加载模型耗时: 150ms
//       [ 解析模型耗时 ]: 80ms
//       [ 内存缓存 ] 存储模型 /models/car.glb (version: 1.0.0)
```

**常用方法:**

##### asyncFetch()

推荐的模型加载方法,集成 IndexedDB 缓存、内存缓存、重试机制、性能监控和模型优化。

```javascript
const model = await asyncFetch(
  url: string,        // 模型 URL
  version?: string,   // 模型版本(用于缓存失效)
  onProgress?: (percent: number) => void,  // 进度回调
  options?: {
    // 重试配置
    maxRetries?: number,      // 最大重试次数(默认: 3)

    // 模型优化选项
    optimizeMaterials?: boolean,  // 是否自动优化材质,合并相同材质(默认: false)
    simplifyGeometry?: boolean,   // 是否简化模型几何体(默认: false)
    simplifyRatio?: number,       // 简化比例 0-1, 0.5=保留50%面(默认: 0.5)
    simplifyOptions?: {
      minFaceCount?: number,      // 最小面数阈值,低于此值不简化(默认: 100)
      preserveUVs?: boolean      // 是否保留UV坐标(默认: true)
    },

    // 缓存控制
    useMemoryCache?: boolean  // 是否使用内存缓存(默认: true, v1.4.2+)
  }
): Promise<THREE.Object3D | null>
```

**缓存层级 (v1.4.2+):**

1. **内存缓存** (最快) - 已解析的 3D 对象，仅需克隆（~2ms）
2. **IndexedDB 缓存** (快) - ArrayBuffer 数据，需要解析（~200ms）
3. **网络加载** (慢) - 完整下载 + 解析（~2s）

**示例:**
```javascript
// 基础用法
const model = await asyncFetch('/models/car.glb', '1.0.0');
if (model) {
  scene.add(model);
}

// 带进度回调
const model = await asyncFetch('/models/car.glb', '1.0.0', (percent) => {
  console.log(`加载进度: ${percent}%`);
});

// 启用材质优化
const model = await asyncFetch('/models/car.glb', '1.0.0', null, {
  optimizeMaterials: true  // 自动合并相同材质
});

// 启用几何体简化(适合大型模型)
const model = await asyncFetch('/models/large-scene.glb', '1.0.0', null, {
  simplifyGeometry: true,      // 启用简化
  simplifyRatio: 0.5,          // 保留 50% 的面
  simplifyOptions: {
    minFaceCount: 500,         // 面数少于 500 的网格不简化
    preserveUVs: true          // 保留 UV 坐标
  }
});

// 禁用内存缓存
const model = await asyncFetch('/models/car.glb', '1.0.0', null, {
  useMemoryCache: false  // 跳过内存缓存
});

// 综合优化
const model = await asyncFetch('/models/complex.glb', '1.0.0', (p) => {
  console.log(`加载: ${p.toFixed(1)}%`);
}, {
  maxRetries: 5,             // 增加重试次数
  optimizeMaterials: true,   // 优化材质
  simplifyGeometry: true,    // 简化几何体
  simplifyRatio: 0.7,        // 保留 70% 的面
  useMemoryCache: true       // 使用内存缓存(默认)
});
```

**性能对比:**

```javascript
// 首次加载
await asyncFetch('/models/car.glb', '1.0.0');
// 耗时: ~2s (网络 + 解析 + 优化)

// 二次加载 (内存缓存命中)
await asyncFetch('/models/car.glb', '1.0.0');
// 耗时: ~2ms (仅克隆对象)

// 二次加载 (仅 IndexedDB 命中,内存缓存被清空)
loader.clearMemoryCache();
await asyncFetch('/models/car.glb', '1.0.0');
// 耗时: ~200ms (从 IndexedDB 读取 + 解析)
```

##### logCacheReport()

输出 IndexedDB 缓存性能报告到控制台。

```javascript
logCacheReport(): void
```

**输出示例:**
```
💾 [ 缓存性能报告 ] ====
  - 总加载次数: 5
  - 缓存命中次数: 3
  - 缓存未命中次数: 2
  - 缓存命中率: 60.00%
  - 总耗时: 2.5s
  - 平均耗时: 500ms
  - 缓存节省时间: ~1.5s
```

##### 内存缓存管理 API (v1.4.2+)

**getMemoryCacheInfo()**

获取内存缓存统计信息。

```javascript
const info = loader.getMemoryCacheInfo();

// 返回值:
{
  totalPaths: 2,      // 缓存的不同模型路径数量
  totalModels: 3,     // 缓存的总模型数量
  details: [
    {
      path: '/models/car.glb',
      versions: ['1.0.0', '1.1.0'],
      count: 2
    },
    {
      path: '/models/truck.glb',
      versions: ['1.0.0'],
      count: 1
    }
  ]
}
```

**clearMemoryCache()**

清空所有内存缓存，释放已缓存的 3D 模型对象内存。

```javascript
const count = loader.clearMemoryCache();
console.log(`释放了 ${count} 个模型`);

// 使用场景: 内存不足时主动释放
if (info.totalModels > 100) {
  loader.clearMemoryCache();
}
```

**deleteMemoryCache(path, version?)**

删除指定模型的内存缓存。

```javascript
// 删除特定版本
loader.deleteMemoryCache('/models/car.glb', '1.0.0');

// 删除所有版本
loader.deleteMemoryCache('/models/car.glb');
```

**使用示例:**

```javascript
import { useGLTFLoader } from '@ridp/threejs/hooks';

const loader = useGLTFLoader({ debug: true });

// 首次加载 - 从网络加载并缓存
const model1 = await loader.asyncFetch('/models/car.glb', '1.0.0');
// [ asyncFetch ] ====> 缓存未命中
// [ 内存缓存 ] 存储模型 /models/car.glb (version: 1.0.0)

// 二次加载 - 从内存缓存获取（极快）
const model2 = await loader.asyncFetch('/models/car.glb', '1.0.0');
// [ 内存缓存命中 ] /models/car.glb (version: 1.0.0)
// [ 内存缓存克隆耗时 ]: 2ms

// 查看内存缓存信息
const info = loader.getMemoryCacheInfo();
console.log(`缓存了 ${info.totalModels} 个模型`);

// 清空内存缓存
loader.clearMemoryCache();

// 再次加载 - 从 IndexedDB 缓存获取
const model3 = await loader.asyncFetch('/models/car.glb', '1.0.0');
// [ asyncFetch ] ====> IndexedDB 缓存命中
```

### useRaycaster()

射线拾取工具,用于鼠标交互和物体选择。

```javascript
const { getIntersects, getPointer } = useRaycaster('app-id');

// 获取鼠标拾取的对象
const { intersects, pointer, x, y } = getIntersects(
  event,          // 鼠标事件
  renderer.domElement,  // 渲染器 DOM
  camera,         // 相机
  objects         // 可拾取对象数组
);

// intersects[0].object - 拾取到的 3D 对象
// pointer - 标准化设备坐标(-1 到 1)
// x, y - 鼠标在容器中的相对坐标
```

**示例:**
```javascript
const canvas = document.getElementById('canvas');
canvas.addEventListener('click', (event) => {
  const { intersects } = getIntersects(event, canvas, camera, scene.children);

  if (intersects.length > 0) {
    const clickedObject = intersects[0].object;
    console.log('点击了:', clickedObject.name);

    // 高亮显示
    clickedObject.material.color.set(0xff0000);
  }
});
```

### useObb()

有向包围盒(OBB)碰撞检测工具。

```javascript
import { useObb, obbObjects, intersectColor } from '@ridp/threejs';

const {
  resetObbs,              // 重置所有 OBB
  initObb,                // 初始化模型的 OBB
  initSingleObbItem,      // 初始化单个对象的 OBB
  addObbFromArray,        // 批量添加 OBB
  getObbObjectByParentUid, // 根据 parentUid 获取 OBB 对象
  checkObbIntersection     // 检测碰撞
} = useObb();
```

**使用示例:**
```javascript
// 1. 标记需要碰撞检测的模型
model.userData.needCheck = true;

// 2. 初始化 OBB
initObb('model-uid', model);

// 3. 碰撞检测
const hasCollision = checkObbIntersection(otherObject, obbObjects);

// 4. 高亮碰撞对象
if (hasCollision) {
  object.material.color.set(intersectColor);
}
```

### useBatchGLTFLoader()

批量模型加载器,支持并发控制和进度追踪。

```javascript
const { loadBatch, loadBatchSequential } = useBatchGLTFLoader();

// 并发加载(推荐)
const results = await loadBatch([
  { url: '/models/part1.glb', version: '1.0.0' },
  { url: '/models/part2.glb', version: '1.0.0' },
  { url: '/models/part3.glb', version: '1.0.0' }
], {
  concurrency: 3,        // 最大并发数
  onProgress: (progress) => {
    console.log(`进度: ${progress.percent.toFixed(2)}%`);
  }
});

// 顺序加载
const results = await loadBatchSequential([
  { url: '/models/part1.glb', version: '1.0.0' },
  { url: '/models/part2.glb', version: '1.0.0' }
]);
```

---

## 工具函数

### 场景辅助工具

```javascript
import {
  createCameraHelper,    // 创建相机辅助器
  createGridHelper,      // 创建网格辅助器
  createBox3Helper,      // 创建包围盒辅助器
  createOrbitControl,    // 创建轨道控制器
  createMapControls,     // 创建地图控制器
  createRaycaster,       // 创建射线投射器
  createAxesHelper,      // 创建坐标轴辅助器
  createArrowHelper,     // 创建箭头辅助器
  createStats            // 创建性能监控器
} from '@ridp/threejs';

// 示例
const gridHelper = createGridHelper(100, 10, 0x888888, 0xcccccc);
scene.add(gridHelper);

const stats = createStats();
document.body.appendChild(stats.dom);
```

**功能说明:**
- `createCameraHelper(camera)` - 创建相机辅助线,显示相机视锥体
- `createGridHelper(size, divisions, color1, color2)` - 创建网格辅助线
  - `size`: 网格大小,默认 150
  - `divisions`: 分段数
  - `color1/color2`: 中心线和其他线的颜色
- `createBox3Helper(model)` - 创建包围盒辅助线
- `createOrbitControl(camera, dom)` - 创建轨道控制器
- `createMapControls(camera, dom)` - 创建地图控制器
- `createRaycaster()` - 创建射线投射器,用于鼠标拾取
- `createAxesHelper(size)` - 创建坐标轴辅助线,默认大小 10
- `createArrowHelper(dir, origin, length, color)` - 创建箭头辅助线
- `createStats()` - 创建性能监控器(基于 stats.js)

### initEnvImage()

初始化场景环境贴图。

```javascript
import { initEnvImage } from '@ridp/threejs';

// EXR 格式
await initEnvImage(scene, '/hdr/studio.exr');

// HDR 格式
await initEnvImage(scene, '/hdr/outdoor.hdr');

// PNG/JPG 格式(作为 CubeTexture)
await initEnvImage(scene, '/textures/env.jpg');
```

### getCommonParent()

查找多个网格对象的公共父节点。

```javascript
import { getCommonParent } from '@ridp/threejs';

// 查找多个选中对象的公共父节点
const meshes = [mesh1, mesh2, mesh3];
const commonParent = getCommonParent(meshes, scene);

console.log('公共父节点:', commonParent);
```

**功能说明:**
- 遍历每个网格的所有父节点,统计出现频率最高的父节点
- 返回的公共父节点可作为分组、操作的目标对象
- 常用于批量操作、场景组织等场景

**使用场景:**
- 批量选择对象时查找共同的容器
- 场景层级分析
- 分组操作前的父节点确定

### modelOptimizer

模型优化工具,用于减少多边形数量和优化材质。

```javascript
import { modelOptimizer } from '@ridp/threejs';

// 获取模型统计信息
const stats = modelOptimizer.getModelStats(model);
console.log('网格数量:', stats.meshCount);
console.log('三角形数:', stats.triangleCount);
console.log('顶点数:', stats.vertexCount);
console.log('材质数:', stats.materialCount);

// 简化模型几何体
const optimized = modelOptimizer.simplifyModel(model, {
  ratio: 0.5,        // 保留 50% 的面
  mergeMaterials: true,  // 合并相同材质
  removeUnused: true     // 移除未使用的顶点
});

// 优化材质
modelOptimizer.optimizeMaterials(model);
```

### disposeThreeObject()

深度释放 3D 对象及其子对象的资源。

```javascript
import { disposeThreeObject } from '@ridp/threejs';

// 释放模型资源
disposeThreeObject(model);

// 释放整个场景
disposeThreeObject(scene);
```

**清理内容:**
- 几何体 (geometry.dispose())
- 材质 (material.dispose())
- 材质中的所有纹理 (texture.dispose())
- 子对象递归清理

**注意:** 此函数会递归遍历对象的所有子对象,确保所有资源都被正确释放,避免内存泄漏。

### sceneRebuilder

渐进式场景重建工具,用于大型模型的分批渲染。

```javascript
import { ProgressiveSceneBuilder } from '@ridp/threejs';

const builder = new ProgressiveSceneBuilder(scene, camera, {
  batchSize: 1000,        // 每批添加的网格数
  delay: 16,              // 批次间延迟(毫秒)
  onProgress: (progress) => {
    console.log(`构建进度: ${progress.percent}%`);
  }
});

await builder.build(model);
```

### 模型序列化工具

用于 Three.js 对象的序列化和反序列化,支持将 3D 对象转换为可存储的数据格式。

```javascript
import {
  object3DToData,
  object3DToDataSync,
  dataToObject3D,
  dataToObject3DSync
} from '@ridp/threejs';

// 异步序列化(推荐,避免阻塞主线程)
const data = await object3DToData(model, 50);

// 同步序列化
const data = object3DToDataSync(model);

// 异步反序列化(推荐,避免阻塞主线程)
const restoredModel = await dataToObject3D(data, 50);

// 同步反序列化
const restoredModel = dataToObject3DSync(data);
```

**功能说明:**
- `object3DToData(object, chunkSize)` - 异步序列化对象,使用分块处理避免阻塞
  - `chunkSize`: 每帧处理的对象数量,默认 50
- `object3DToDataSync(object)` - 同步序列化对象
- `dataToObject3D(data, chunkSize)` - 异步反序列化数据为 3D 对象
- `dataToObject3DSync(data)` - 同步反序列化数据为 3D 对象

**序列化内容:**
- 对象类型、名称、位置、旋转、缩放
- 几何体数据(顶点、索引、UV 等)
- 材质数据(颜色、纹理、属性等)
- userData 自定义数据
- 子对象层级关系

**使用场景:**
- 场景状态保存和恢复
- 模型数据的本地存储
- 跨页面/跨会话的模型传输

### 对象查询工具

基于 userData 的对象查找工具。

```javascript
import { getObjectByUserData, getRootObj } from '@ridp/threejs';

// 向下遍历查找首个匹配 userData 的对象
const target = getObjectByUserData(mesh, 'type', 'wheel');

// 向上查找根节点（根据 userData 判定）
const root = getRootObj(mesh, 'isRoot', true);
```

**功能说明:**
- `getObjectByUserData(obj, key, value)` - 从指定对象开始向下遍历,查找首个 userData[key]=value 的对象(包括自身)
- `getRootObj(obj, rootKey, value)` - 从指定对象开始向上查找父级,直到找到 userData[rootKey]=value 的根对象

### CSS3D 辅助工具

```javascript
import { createInfoPlane, createTagPlane } from '@ridp/threejs';

// 从 DOM ID 创建 CSS3D 信息面板
const infoPlane = createInfoPlane('my-info-panel', [0.3, 0.3, 0.3]);
infoPlane.position.set(0, 10, 0);
scene.add(infoPlane);

// 创建 CSS3D 标签面板
const tagPlane = createTagPlane('设备名称', 0.01);
tagPlane.position.set(5, 5, 5);
scene.add(tagPlane);
```

**功能说明:**
- `createInfoPlane(id, scale)` - 从页面中已存在的 DOM 元素创建 CSS3D 精灵
  - `id`: DOM 元素的 ID
  - `scale`: 缩放比例数组 `[x, y, z]`,默认 `[0.3, 0.3, 0.3]`
  - ⚠️ 注意: HTML 元素不能设置为绝对定位
- `createTagPlane(text, scale)` - 创建带文本的 CSS3D 标签
  - `text`: 标签文本内容
  - `scale`: 统一缩放比例(三个维度相同)

### CacheMonitor

缓存性能监控工具。

```javascript
import { cacheMonitor } from '@ridp/threejs';

// 获取缓存统计
const stats = cacheMonitor.getStats();
console.log('命中率:', stats.hitRate);
console.log('平均耗时:', stats.avgLoadTime);

// 监听缓存事件
cacheMonitor.on('hit', (key) => {
  console.log('缓存命中:', key);
});

cacheMonitor.on('miss', (key) => {
  console.log('缓存未命中:', key);
});
```

### RetryHelper

重试机制工具,用于处理不稳定的网络请求。

```javascript
import { RetryHelper, ModelLoadError } from '@ridp/threejs';

const retryHelper = new RetryHelper({
  maxRetries: 3,        // 最大重试次数
  baseDelay: 1000,      // 基础延迟(毫秒)
  maxDelay: 10000,      // 最大延迟(毫秒)
  backoffMultiplier: 2  // 退避乘数
});

try {
  const result = await retryHelper.execute(async () => {
    const response = await fetch('/models/car.glb');
    if (!response.ok) {
      throw new ModelLoadError('加载失败', response.status);
    }
    return response.json();
  });
} catch (error) {
  console.error('重试失败:', error);
}
```

### PredictiveLoader

预测性加载工具,根据用户行为预加载可能需要的资源。

```javascript
import { PredictiveLoader } from '@ridp/threejs';

const loader = new PredictiveLoader({
  maxPrefetch: 3,       // 最大预加载数
  priority: 'recent'    // 优先级策略
});

// 预加载模型
loader.prefetch('/models/next-part.glb', '1.0.0');

// 根据用户行为预测
loader.predict(userBehavior, availableModels);
```

---

## 实例类

### IDBCache

IndexedDB 缓存管理类。

```javascript
import { IDBCache } from '@ridp/threejs';

const cache = new IDBCache('my-cache-db', 1);

// 保存模型到缓存
await cache.saveModel('model-key', modelData, {
  version: '1.0.0',
  timestamp: Date.now()
});

// 从缓存加载模型
const data = await cache.loadCachedModel('model-key', '1.0.0');

// 清除特定模型缓存
await cache.deleteModel('model-key');

// 清空所有缓存
await cache.clear();

// 获取缓存统计
const stats = await cache.getStats();
```

---

## 完整示例

### Vue 3 组件示例

```vue
<template>
  <div class="canvas-container" ref="containerRef"></div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { ThreeIns, useGLTFLoader, initEnvImage, ViewType } from '@ridp/threejs';
import * as THREE from 'three';

const containerRef = ref(null);
let threeJsIns = null;
const { asyncFetch, logCacheReport } = useGLTFLoader();

onMounted(async () => {
  // 1. 初始化场景
  threeJsIns = new ThreeIns(containerRef.value, {
    css3d: false,
    renderType: 'loop',
    initListener: true,
    initialFov: 75,
    control: {
      init: true,
      options: {
        maxDistance: Infinity,
        minDistance: 0,
        enablePan: true,
        enableDamping: true,
        dampingFactor: 0.25,
      },
    },
  });

  // 2. 加载环境贴图
  await initEnvImage(threeJsIns.scene, '/hdr/studio.exr');

  // 3. 加载模型
  const [err, model] = await asyncFetch('/models/car.glb', '1.0.0', {
    onProgress: (progress) => {
      const percent = (progress.loaded / progress.total * 100).toFixed(2);
      console.log(`加载进度: ${percent}%`);
    }
  });

  if (model) {
    threeJsIns.scene.add(model);

    // 4. 设置视角
    threeJsIns.setView(model, ViewType.ISO, {
      scale: 1.0,
      showBox: false,
      animate: true,
      duration: 1000
    });
  }

  // 5. 查看缓存性能
  logCacheReport();
});

onUnmounted(() => {
  // 清理资源
  if (threeJsIns) {
    threeJsIns.dispose();
  }
});
</script>

<style scoped>
.canvas-container {
  width: 100%;
  height: 100vh;
}
</style>
```

### 多模型加载示例

```javascript
import { ThreeIns, useBatchGLTFLoader, ViewType } from '@ridp/threejs';

const threeJsIns = new ThreeIns('#container', { /* ... */ });
const { loadBatch } = useBatchGLTFLoader();

// 批量加载多个模型
const models = [
  { url: '/models/floor.glb', version: '1.0.0' },
  { url: '/models/wall.glb', version: '1.0.0' },
  { url: '/models/roof.glb', version: '1.0.0' },
  { url: '/models/furniture.glb', version: '1.0.0' }
];

const results = await loadBatch(models, {
  concurrency: 2,
  onProgress: (progress) => {
    console.log(`加载进度: ${progress.percent}%`);
    console.log(`已完成: ${progress.loaded}/${progress.total}`);
  }
});

// 添加所有模型到场景
results.forEach(result => {
  if (result.model) {
    threeJsIns.scene.add(result.model);
  }
});

// 自动适配视角
const group = new THREE.Group();
results.forEach(r => r.model && group.add(r.model));
threeJsIns.setView(group, ViewType.TOP, { scale: 0.8 });
```

### 交互式场景示例

```javascript
import { ThreeIns, useRaycaster, useObb } from '@ridp/threejs';

const threeJsIns = new ThreeIns('#container', { /* ... */ });
const { getIntersects } = useRaycaster('app');
const { initObb, checkObbIntersection } = useObb();

// 加载模型后初始化碰撞检测
model.userData.needCheck = true;
initObb('model-1', model);

// 点击事件
threeJsIns.renderer.domElement.addEventListener('click', (event) => {
  const { intersects } = getIntersects(
    event,
    threeJsIns.renderer.domElement,
    threeJsIns.camera,
    [model]
  );

  if (intersects.length > 0) {
    const object = intersects[0].object;
    console.log('点击了:', object.name);

    // 检测碰撞
    const hasCollision = checkObbIntersection(object, obbObjects);
    if (hasCollision) {
      object.material.color.set(0xff0000);
    }
  }
});
```

---

## 性能优化建议

### 1. 使用模型缓存和自动优化

对于重复访问的模型,启用 IndexedDB 缓存可以显著提升加载速度。同时可以启用材质优化和几何体简化来提升渲染性能:

```javascript
// 首次加载: ~2s (含优化)
await asyncFetch('/models/large-scene.glb', '1.0.0', {
  optimizeMaterials: true,   // 合并相同材质,减少 draw calls
  simplifyGeometry: true,    // 简化几何体,减少三角形数
  simplifyRatio: 0.5         // 保留 50% 的面
});

// 二次加载: ~200ms (从缓存读取,仍需优化)
await asyncFetch('/models/large-scene.glb', '1.0.0', {
  optimizeMaterials: true,
  simplifyGeometry: true,
  simplifyRatio: 0.5
});
```

**优化选项说明:**

- **`optimizeMaterials`** (材质优化)
  - 自动合并相同属性的材质
  - 减少材质切换次数
  - 降低 GPU draw calls
  - 适用场景: 模型中有很多相似材质的对象

- **`simplifyGeometry`** (几何体简化)
  - 减少模型的三角形数量
  - 显著提升渲染性能
  - 适合大型场景或低性能设备
  - 可配置简化比例和最小面数阈值

- **`simplifyRatio`** (简化比例)
  - `0.8` - 保留 80% 的面(轻度简化)
  - `0.5` - 保留 50% 的面(中度简化,推荐)
  - `0.3` - 保留 30% 的面(重度简化,适合远景模型)

**性能对比:**
```
未优化模型: 50万三角形, 120个材质
优化后:    25万三角形 (50%减少), 45个材质 (62%减少)
FPS提升:   30 FPS -> 55 FPS (83%提升)
```

### 2. 批量加载优化

使用批量加载时,合理控制并发数:

```javascript
// 推荐: 根据设备性能动态调整
const concurrency = navigator.hardwareConcurrency || 4;
await loadBatch(models, { concurrency });
```

### 3. 手动模型优化

如果需要在加载后手动优化模型:

```javascript
import { modelOptimizer } from '@ridp/threejs';

// 材质优化
modelOptimizer.optimizeMaterials(model);

// 几何体简化
modelOptimizer.simplifyModel(model, 0.5, {
  minFaceCount: 500,
  preserveUVs: true
});

// 查看优化效果
const stats = modelOptimizer.getModelStats(model);
console.log('优化后统计:', stats);
```

### 4. 渐进式渲染

对于超大场景,使用渐进式渲染避免长时间卡顿:

```javascript
import { ProgressiveSceneBuilder } from '@ridp/threejs';

const builder = new ProgressiveSceneBuilder(scene, camera, {
  batchSize: 1000,
  delay: 16
});
await builder.build(model);
```

### 5. 合理使用阴影

阴影是性能杀手,仅在必要时启用:

```javascript
renderer.shadowMap.enabled = true;  // 启用阴影
renderer.shadowMap.type = THREE.PCFSoftShadowMap;  // 软阴影

// 仅重要对象投射阴影
importantObject.castShadow = true;
otherObjects.castShadow = false;
```

---

## 常见问题

### Q: 如何处理模型加载失败?

A: 使用 `asyncFetch` 的重试机制:

```javascript
const [err, model] = await asyncFetch(url, version, {
  retryCount: 5,
  timeout: 60000
});

if (err || !model) {
  console.error('模型加载失败:', err);
  // 显示错误提示或加载备用模型
}
```

### Q: 如何清理模型缓存?

A: 使用 `clearCache` 方法:

```javascript
const { clearCache } = useGLTFLoader();
await clearCache();  // 清空所有缓存

// 或使用 IDBCache 实例
import { IDBCache } from '@ridp/threejs';
const cache = new IDBCache();
await cache.clear();
```

### Q: 模型显示太小或太大?

A: 使用 `setView` 的 `scale` 参数:

```javascript
// 模型太小 - 放大
threeJsIns.setView(model, ViewType.ISO, { scale: 2.0 });

// 模型太大 - 缩小
threeJsIns.setView(model, ViewType.ISO, { scale: 0.5 });
```

> **注意**: 旧方法 `frameArea()` 已弃用，请使用 `setView()` 代替。

### Q: 如何监听窗口大小变化?

A: 如果 `initListener: true`,会自动监听。否则手动监听:

```javascript
window.addEventListener('resize', () => {
  threeJsIns.updateCameraFOV();
  threeJsIns.camera.updateProjectionMatrix();
  threeJsIns.renderer.setSize(
    threeJsIns.domElement.clientWidth,
    threeJsIns.domElement.clientHeight
  );
});
```

### Q: 如何导出场景截图?

A: 使用 `renderer.domElement.toDataURL()`:

```javascript
function captureScreenshot() {
  threeJsIns.render();  // 确保最新帧已渲染
  const dataURL = threeJsIns.renderer.domElement.toDataURL('image/png');
  const link = document.createElement('a');
  link.download = 'screenshot.png';
  link.href = dataURL;
  link.click();
}
```

---

## 更新日志

### v1.4.2 (2026-01-07)

**🎉 重大更新:**

- ✨ **新增内存缓存机制**: 自动缓存已解析的 3D 模型对象，避免重复解析
  - 首次加载后，二次加载仅需 ~2ms（对象克隆）
  - 相比完整加载流程，速度提升可达 **100倍以上**
  - 新增 `clearMemoryCache()` - 清空内存缓存
  - 新增 `getMemoryCacheInfo()` - 获取内存缓存统计
  - 新增 `deleteMemoryCache(path, version?)` - 删除指定缓存
  - 支持 `useMemoryCache` 选项控制是否使用内存缓存

- ✨ **新增调试模式控制**:
  - `useGLTFLoader` 现在支持初始化配置 `{ debug: boolean }`
  - 统一控制所有内部函数的日志输出
  - 生产环境默认静默，开发环境可开启详细日志
  - 错误日志始终输出，不受 debug 配置影响

- 🐛 **修复 Web Worker 在 npm 包中无法加载的问题**:
  - 使用 `?worker` 语法正确导入 Worker
  - 配置 `base: './'` 使用相对路径解析
  - Worker 文件正确打包到 `dist/assets/` 目录
  - 修复了之前 404 错误和 MIME 类型错误的问题

**API 变更:**

```javascript
// 旧 API (v1.4.1 及之前)
const loader = useGLTFLoader();
const model = await loader.asyncFetch(url, version, null, { debug: true });

// 新 API (v1.4.2+)
const loader = useGLTFLoader({ debug: true });
const model = await loader.asyncFetch(url, version);

// 新增内存缓存管理
const info = loader.getMemoryCacheInfo();
loader.clearMemoryCache();
loader.deleteMemoryCache(url, version);
```

**性能提升:**

```
加载场景 (v1.4.1):
- 首次: ~2s (网络 + 解析)
- 二次: ~200ms (IndexedDB 读取 + 解析)

加载场景 (v1.4.2+):
- 首次: ~2s (网络 + 解析)
- 二次: ~2ms (内存缓存克隆)
- 提升: 100倍
```

### v1.4.1 (2025-XX-XX)

- 🎯 优化 `setView` 方法的 scale 参数计算逻辑
- 🔧 修复水平 FOV 超过 180° 导致的负数距离问题
- ✨ 移除 FRONT/BACK/BOTTOM 视角,保留 TOP/RIGHT/LEFT/ISO
- ⚡ 优化 OrbitControls 距离限制的自动处理
- 🐛 修复相机 near/far 平面裁剪问题
- ⚠️ **弃用**: `useThreeJs()` Hook - 推荐使用 **ThreeIns 类** (功能更完整,不限框架)
- ⚠️ **弃用**: `frameArea()` 方法 - 推荐使用 **setView() 方法** (支持多视角)
- ✨ **新增**: `asyncFetch` 支持自动材质优化 (`optimizeMaterials`)
- ✨ **新增**: `asyncFetch` 支持几何体简化 (`simplifyGeometry`)
- ✨ **新增**: `modelOptimizer.simplifyModel()` 方法 - 整体模型简化
- 📦 **增强**: 完整的模型优化工作流,支持加载时自动优化

### v1.3.2 (2025-01-05)

- ✨ 新增 Web Worker 支持,优化 GLTF 解析性能
- 📦 增强模型缓存机制
- 🎨 新增 `modelOptimizer` 工具
- ⚡ 性能优化:减少重复计算

### v1.3.0 (2024-12-XX)

- ✨ 新增 `useBatchGLTFLoader` 批量加载 Hook
- 🎯 新增 `setView` 方法,支持多视角切换
- 📊 新增 `CacheMonitor` 缓存监控工具
- 🔧 新增 `RetryHelper` 重试机制

完整更新日志请查看 [CHANGELOG.md](./CHANGELOG.md)

---

## 许可证

MIT License

---

## 支持

- 📧 Email: support@example.com
- 🐛 Issues: [GitHub Issues](https://github.com/example/threejs/issues)
- 📖 文档: [在线文档](https://example.com/docs)

---

## 相关资源

- [Three.js 官方文档](https://threejs.org/docs/)
- [Three.js 示例](https://threejs.org/examples/)
- [GLTF 文件格式规范](https://github.com/KhronosGroup/glTF)