@ridp/threejs 1.3.3 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.md CHANGED
@@ -1,113 +1,1205 @@
1
- # @ridp/threejs
1
+ # @ridp/threejs
2
2
 
3
+ 基于 Three.js 的 3D 可视化工具库,提供场景管理、模型加载、性能优化等开箱即用的功能。
4
+
5
+ > **📢 重要提示**:
6
+ > - ⚠️ `useThreeJs()` Hook 已弃用,请使用 **ThreeIns 类** (功能更强大,不限框架)
7
+ > - ⚠️ `frameArea()` 方法已弃用,请使用 **setView() 方法** (支持多视角切换)
8
+ >
9
+ > **快速迁移**:
10
+ > ```javascript
11
+ > // ❌ 旧用法 (已弃用)
12
+ > const { scene, camera } = useThreeJs('#container', opts);
13
+ > threeIns.frameArea(model, 1.0);
14
+ >
15
+ > // ✅ 新用法 (推荐)
16
+ > const threeIns = new ThreeIns('#container', opts);
17
+ > threeIns.setView(model, ViewType.ISO, { scale: 1.0 });
18
+ > ```
19
+
20
+ ## 特性
21
+
22
+ - 🚀 **开箱即用**: 零配置快速搭建 Three.js 场景
23
+ - 📦 **智能缓存**: 基于 IndexedDB 的模型缓存机制,大幅提升二次加载速度
24
+ - 🎯 **视角控制**: 内置多种视角(俯视、侧视、等轴测)和平滑过渡动画
25
+ - 🔧 **性能优化**: 模型优化、批量加载、渐进式渲染等性能优化工具
26
+ - 🎨 **辅助工具**: 包围盒检测、射线拾取、碰撞检测等实用工具
27
+ - 💡 **TypeScript 支持**: 完整的类型定义和 JSDoc 文档
28
+ - 🌐 **框架无关**: 可在任何 JavaScript/TypeScript 项目中使用(Vue/React/Angular/原生JS)
3
29
 
4
30
  ## 安装
5
31
 
32
+ ### 依赖要求
33
+
6
34
  本库依赖以下 peer dependencies:
7
35
 
8
36
  ```json
9
- "peerDependencies": {
37
+ {
38
+ "peerDependencies": {
10
39
  "dexie": ">=4.0.0 <5.0.0",
11
40
  "vue": ">=3.0.0 <4.0.0",
12
41
  "three": ">=0.178.0 <1.0.0"
42
+ }
13
43
  }
14
44
  ```
15
45
 
16
- 请一并安装
46
+ ### NPM 安装
17
47
 
18
48
  ```bash
19
-
20
49
  npm install @ridp/threejs dexie@^4 vue@^3 three@^0.178
50
+ ```
51
+
52
+ ### Yarn 安装
53
+
54
+ ```bash
55
+ yarn add @ridp/threejs dexie@^4 vue@^3 three@^0.178
56
+ ```
57
+
58
+ ### PNPM 安装
59
+
60
+ ```bash
61
+ pnpm add @ridp/threejs dexie@^4 vue@^3 three@^0.178
62
+ ```
63
+
64
+ ## 快速开始
65
+
66
+ ### 基础场景初始化
67
+
68
+ ```javascript
69
+ import { ThreeIns } from '@ridp/threejs';
70
+
71
+ // 创建 3D 场景实例
72
+ const threeJsIns = new ThreeIns('#container', {
73
+ css3d: true, // 启用 CSS3D 渲染器
74
+ renderType: 'loop', // 渲染循环类型
75
+ initListener: true, // 初始化事件监听
76
+ initialFov: 75, // 初始 FOV
77
+ control: { // OrbitControls 配置
78
+ init: true,
79
+ options: {
80
+ maxDistance: Infinity,
81
+ minDistance: 0,
82
+ enablePan: true,
83
+ enableDamping: true,
84
+ dampingFactor: 0.25,
85
+ },
86
+ },
87
+ });
88
+
89
+ // 访问核心对象
90
+ console.log(threeJsIns.scene); // THREE.Scene
91
+ console.log(threeJsIns.camera); // THREE.PerspectiveCamera
92
+ console.log(threeJsIns.renderer); // THREE.WebGLRenderer
93
+ console.log(threeJsIns.control); // THREE.OrbitControls
94
+ ```
95
+
96
+ ### 加载 GLTF 模型
97
+
98
+ ```javascript
99
+ import { useGLTFLoader, initEnvImage } from '@ridp/threejs';
100
+
101
+ const { asyncFetch, logCacheReport } = useGLTFLoader();
102
+
103
+ // 加载模型(带缓存)
104
+ async function loadModel() {
105
+ const modelUrl = '/models/car.glb';
106
+ const version = '1.0.0';
107
+
108
+ const [err, model] = await asyncFetch(modelUrl, version);
109
+
110
+ if (model) {
111
+ threeJsIns.scene.add(model);
112
+ threeJsIns.setView(model, ViewType.ISO, {
113
+ scale: 1.0,
114
+ showBox: true,
115
+ animate: true,
116
+ duration: 1000
117
+ });
118
+ }
119
+
120
+ // 查看缓存性能报告
121
+ logCacheReport();
122
+ }
123
+
124
+ // 加载环境贴图
125
+ await initEnvImage(threeJsIns.scene, '/hdr/studio.exr');
126
+ loadModel();
127
+ ```
128
+
129
+ ## 核心 API
130
+
131
+ ### ThreeIns 类
132
+
133
+ 主要的 3D 场景管理类。
134
+
135
+ #### 构造函数
136
+
137
+ ```javascript
138
+ new ThreeIns(selector: string, options: ThreeInsOptions)
139
+ ```
140
+
141
+ **参数:**
142
+ - `selector`: DOM 元素选择器(如 `#canvas-container`)
143
+ - `options`: 配置对象
144
+ - `css3d`: 是否启用 CSS3D 渲染器(默认: `false`)
145
+ - `renderType`: 渲染循环类型,`'loop'` 或 `'ondemand'`(默认: `'loop'`)
146
+ - `initListener`: 是否自动监听窗口大小变化(默认: `true`)
147
+ - `initialFov`: 初始垂直 FOV(默认: `50`)
148
+ - `control`: OrbitControls 配置
149
+ - `init`: 是否初始化控制器(默认: `true`)
150
+ - `options`: OrbitControls 配置对象
151
+
152
+ #### 实例属性
153
+
154
+ - `scene: THREE.Scene` - 场景对象
155
+ - `camera: THREE.PerspectiveCamera` - 相机对象
156
+ - `renderer: THREE.WebGLRenderer` - WebGL 渲染器
157
+ - `css3DRenderer: THREE.CSS3DRenderer` - CSS3D 渲染器(如果启用)
158
+ - `control: THREE.OrbitControls` - 轨道控制器
159
+ - `domElement: HTMLElement` - 容器 DOM 元素
160
+
161
+ #### 主要方法
162
+
163
+ ##### setView()
164
+
165
+ 设置模型视角。
166
+
167
+ ```javascript
168
+ threeJsIns.setView(
169
+ model: Object3D,
170
+ viewType: ViewType,
171
+ options?: {
172
+ scale?: number, // 缩放比例,1=占满画布,0.5=50%,2=0%(默认: 1)
173
+ showBox?: boolean, // 是否显示包围盒(默认: false)
174
+ animate?: boolean, // 是否启用动画(默认: false)
175
+ duration?: number, // 动画时长(毫秒,默认: 1000)
176
+ offset?: Vector3 // 中心点偏移
177
+ }
178
+ ): void
179
+ ```
180
+
181
+ **视角类型 (ViewType):**
182
+ - `ViewType.TOP` - 俯视(从上往下)
183
+ - `ViewType.RIGHT` - 右视图(从右往左)
184
+ - `ViewType.LEFT` - 左侧视(从左往右)
185
+ - `ViewType.ISO` - 等轴测视角(对角线上方俯视)
186
+
187
+ **示例:**
188
+ ```javascript
189
+ // 设置等轴测视角,模型占满画布
190
+ threeJsIns.setView(model, ViewType.ISO, {
191
+ scale: 1.0,
192
+ showBox: true,
193
+ animate: true,
194
+ duration: 800
195
+ });
196
+
197
+ // 设置俯视,模型缩小到 50%
198
+ threeJsIns.setView(model, ViewType.TOP, {
199
+ scale: 0.5,
200
+ animate: true
201
+ });
202
+ ```
203
+
204
+ ##### frameArea()
205
+
206
+ > **⚠️ 已弃用** - 此方法仅为兼容旧版本保留,请使用 `setView()` 方法代替。
207
+
208
+ 自动调整相机位置使模型完整显示在视口中(默认使用等轴测视角)。
209
+
210
+ ```javascript
211
+ threeJsIns.frameArea(
212
+ model: Object3D,
213
+ scale: number = 1,
214
+ options?: {
215
+ showBox?: boolean, // 是否显示包围盒
216
+ offset?: Vector3 // 中心点偏移
217
+ }
218
+ ): void
219
+ ```
220
+
221
+ **参数说明:**
222
+ - `scale`: 显示比例
223
+ - `1.0` - 模型占满画布
224
+ - `0.5` - 模型占画布 50%
225
+ - `2.0` - 模型放大到 200%
226
+
227
+ **示例:**
228
+ ```javascript
229
+ // ❌ 旧用法(已弃用)
230
+ threeJsIns.frameArea(model, 1.0);
231
+
232
+ // ✅ 推荐用法 - 使用 setView
233
+ threeJsIns.setView(model, ViewType.ISO, { scale: 1.0 });
234
+
235
+ // ❌ 旧用法(已弃用)
236
+ threeJsIns.frameArea(model, 0.5, { showBox: true });
237
+
238
+ // ✅ 推荐用法 - 使用 setView
239
+ threeJsIns.setView(model, ViewType.ISO, {
240
+ scale: 0.5,
241
+ showBox: true,
242
+ animate: true
243
+ });
244
+ ```
245
+
246
+ **迁移指南:**
247
+ | 旧用法 (frameArea) | 新用法 (setView) |
248
+ |-------------------|------------------|
249
+ | `frameArea(model, 1.0)` | `setView(model, ViewType.ISO, { scale: 1.0 })` |
250
+ | `frameArea(model, 0.5)` | `setView(model, ViewType.ISO, { scale: 0.5 })` |
251
+ | `frameArea(model, 2.0)` | `setView(model, ViewType.ISO, { scale: 2.0 })` |
252
+
253
+ ##### updateCameraFOV()
254
+
255
+ 根据画布宽高比动态调整相机 FOV。
256
+
257
+ ```javascript
258
+ threeJsIns.updateCameraFOV(): void
259
+ ```
260
+
261
+ 当窗口大小变化时自动调用,确保模型在不同宽高比下正确显示。
262
+
263
+ ##### addAnimate()
264
+
265
+ 添加自定义动画函数到渲染循环。
266
+
267
+ ```javascript
268
+ threeJsIns.addAnimate(fn: Function): void
269
+ ```
270
+
271
+ **示例:**
272
+ ```javascript
273
+ let time = 0;
274
+ threeJsIns.addAnimate(() => {
275
+ time += 0.01;
276
+ model.rotation.y = time;
277
+ });
278
+ ```
279
+
280
+ ##### dispose()
281
+
282
+ 销毁场景,释放所有资源。
283
+
284
+ ```javascript
285
+ threeJsIns.dispose(): void
286
+ ```
287
+
288
+ **清理内容:**
289
+ - 停止渲染循环
290
+ - 移除事件监听器
291
+ - 释放几何体、材质、纹理
292
+ - 清理 Stats 性能监控
293
+ - 销毁 OrbitControls
294
+
295
+ ---
296
+
297
+ ## Hooks
298
+
299
+ ### useThreeJs()
300
+
301
+ > **⚠️ 已弃用** - 此 Hook 仅为兼容旧版本保留,请使用 **ThreeIns 类** 代替。
302
+
303
+ Vue 3 组合式 API 风格的 Three.js 场景初始化 Hook。
304
+
305
+ **为什么要弃用?**
306
+ - ❌ 功能被 ThreeIns 类完全覆盖
307
+ - ❌ 缺少视角切换功能 (setView)
308
+ - ❌ 性能优化不如 ThreeIns 类
309
+ - ❌ 仅限 Vue 项目使用
310
+ - ❌ 资源清理不够完善
311
+
312
+ **推荐迁移到 ThreeIns 类:**
313
+
314
+ ```javascript
315
+ // ❌ 旧用法 (useThreeJs Hook - 已弃用)
316
+ import { useThreeJs } from '@ridp/threejs';
317
+ const { scene, camera, renderer, control } = useThreeJs('#container', { ... });
21
318
 
319
+ // ✅ 推荐用法 (ThreeIns 类)
320
+ import { ThreeIns, ViewType } from '@ridp/threejs';
321
+ const threeJsIns = new ThreeIns('#container', {
322
+ css3d: true,
323
+ renderType: 'loop',
324
+ initListener: true,
325
+ initialFov: 75,
326
+ control: { init: true, options: { ... } }
327
+ });
328
+
329
+ // 访问核心对象
330
+ console.log(threeJsIns.scene); // THREE.Scene
331
+ console.log(threeJsIns.camera); // THREE.PerspectiveCamera
332
+ console.log(threeJsIns.renderer); // THREE.WebGLRenderer
333
+ console.log(threeJsIns.control); // THREE.OrbitControls
334
+
335
+ // 使用强大的 setView 方法
336
+ threeJsIns.setView(model, ViewType.ISO, { scale: 1.0, animate: true });
337
+ ```
338
+
339
+ **迁移对照表:**
340
+
341
+ | 功能 | useThreeJs (已弃用) | ThreeIns 类 (推荐) |
342
+ |------|-------------------|-------------------|
343
+ | 初始化场景 | `useThreeJs('#el', opts)` | `new ThreeIns('#el', opts)` |
344
+ | 访问 scene | `const { scene } = useThreeJs(...)` | `threeJsIns.scene` |
345
+ | 访问 camera | `const { camera } = useThreeJs(...)` | `threeJsIns.camera` |
346
+ | 添加动画 | `addAnimate(fn)` | `threeJsIns.addAnimate(fn)` |
347
+ | 视角切换 | ❌ 不支持 | ✅ `setView(model, ViewType.ISO)` |
348
+ | 资源清理 | `dispose()` | ✅ `threeJsIns.dispose()` |
349
+ | 适用范围 | 仅 Vue 3 | 所有 JS/TS 项目 |
350
+
351
+ ---
352
+
353
+ ### useGLTFLoader()
354
+
355
+ GLTF 模型加载器,支持 IndexedDB 缓存。
356
+
357
+ ```javascript
358
+ const {
359
+ // 核心加载方法
360
+ load, // 基础加载方法
361
+ asyncLoad, // Promise 异步加载
362
+ asyncFetch, // 带缓存和重试的异步加载(推荐)
363
+ asyncCacheLoad, // [已弃用] 旧版缓存加载
364
+
365
+ // 批量加载
366
+ loadBatch, // 批量加载模型
367
+ loadBatchSequential, // 顺序批量加载
368
+
369
+ // 缓存管理
370
+ clearCache, // 清空缓存
371
+ invalidateCache, // 使缓存失效
372
+ getCacheStats, // 获取缓存统计
373
+ logCacheReport, // 输出缓存性能报告
374
+
375
+ // 性能监控
376
+ cacheMonitor // 缓存监控器实例
377
+ } = useGLTFLoader();
378
+ ```
379
+
380
+ **常用方法:**
381
+
382
+ ##### asyncFetch()
383
+
384
+ 推荐的模型加载方法,集成缓存、重试、性能监控和模型优化。
385
+
386
+ ```javascript
387
+ const [err, model] = await asyncFetch(
388
+ url: string, // 模型 URL
389
+ version?: string, // 模型版本(用于缓存失效)
390
+ options?: {
391
+ // 进度和重试
392
+ onProgress?: (progress: ProgressEvent) => void,
393
+ maxRetries?: number, // 最大重试次数(默认: 3)
394
+
395
+ // 模型优化选项
396
+ optimizeMaterials?: boolean, // 是否自动优化材质,合并相同材质(默认: false)
397
+ simplifyGeometry?: boolean, // 是否简化模型几何体(默认: false)
398
+ simplifyRatio?: number, // 简化比例 0-1, 0.5=保留50%面(默认: 0.5)
399
+ simplifyOptions?: {
400
+ minFaceCount?: number, // 最小面数阈值,低于此值不简化(默认: 100)
401
+ preserveUVs?: boolean // 是否保留UV坐标(默认: true)
402
+ }
403
+ }
404
+ ): Promise<[Error | null, THREE.Object3D | null]>
405
+ ```
406
+
407
+ **示例:**
408
+ ```javascript
409
+ // 基础用法
410
+ const [err, model] = await asyncFetch('/models/car.glb', '1.0.0');
411
+ if (model) {
412
+ scene.add(model);
413
+ }
414
+
415
+ // 带进度回调
416
+ const [err, model] = await asyncFetch('/models/car.glb', '1.0.0', {
417
+ onProgress: (progress) => {
418
+ const percent = (progress.loaded / progress.total * 100).toFixed(2);
419
+ console.log(`加载进度: ${percent}%`);
420
+ },
421
+ maxRetries: 5
422
+ });
423
+
424
+ // 启用材质优化
425
+ const [err, model] = await asyncFetch('/models/car.glb', '1.0.0', {
426
+ optimizeMaterials: true // 自动合并相同材质
427
+ });
428
+
429
+ // 启用几何体简化(适合大型模型)
430
+ const [err, model] = await asyncFetch('/models/large-scene.glb', '1.0.0', {
431
+ simplifyGeometry: true, // 启用简化
432
+ simplifyRatio: 0.5, // 保留 50% 的面
433
+ simplifyOptions: {
434
+ minFaceCount: 500, // 面数少于 500 的网格不简化
435
+ preserveUVs: true // 保留 UV 坐标
436
+ }
437
+ });
438
+
439
+ // 综合优化
440
+ const [err, model] = await asyncFetch('/models/complex.glb', '1.0.0', {
441
+ optimizeMaterials: true, // 优化材质
442
+ simplifyGeometry: true, // 简化几何体
443
+ simplifyRatio: 0.7, // 保留 70% 的面
444
+ onProgress: (p) => console.log(`加载: ${(p.loaded/p.total*100).toFixed(1)}%`)
445
+ });
446
+ ```
447
+
448
+ ##### logCacheReport()
449
+
450
+ 输出缓存性能报告到控制台。
451
+
452
+ ```javascript
453
+ logCacheReport(): void
454
+ ```
455
+
456
+ **输出示例:**
457
+ ```
458
+ 💾 [ 缓存性能报告 ] ====
459
+ - 总加载次数: 5
460
+ - 缓存命中次数: 3
461
+ - 缓存未命中次数: 2
462
+ - 缓存命中率: 60.00%
463
+ - 总耗时: 2.5s
464
+ - 平均耗时: 500ms
465
+ - 缓存节省时间: ~1.5s
466
+ ```
467
+
468
+ ### useRaycaster()
469
+
470
+ 射线拾取工具,用于鼠标交互和物体选择。
471
+
472
+ ```javascript
473
+ const { getIntersects, getPointer } = useRaycaster('app-id');
474
+
475
+ // 获取鼠标拾取的对象
476
+ const { intersects, pointer, x, y } = getIntersects(
477
+ event, // 鼠标事件
478
+ renderer.domElement, // 渲染器 DOM
479
+ camera, // 相机
480
+ objects // 可拾取对象数组
481
+ );
482
+
483
+ // intersects[0].object - 拾取到的 3D 对象
484
+ // pointer - 标准化设备坐标(-1 到 1)
485
+ // x, y - 鼠标在容器中的相对坐标
486
+ ```
487
+
488
+ **示例:**
489
+ ```javascript
490
+ const canvas = document.getElementById('canvas');
491
+ canvas.addEventListener('click', (event) => {
492
+ const { intersects } = getIntersects(event, canvas, camera, scene.children);
493
+
494
+ if (intersects.length > 0) {
495
+ const clickedObject = intersects[0].object;
496
+ console.log('点击了:', clickedObject.name);
497
+
498
+ // 高亮显示
499
+ clickedObject.material.color.set(0xff0000);
500
+ }
501
+ });
22
502
  ```
23
503
 
24
- ## Hooks
25
- - useThreeJs
26
- 声明:
27
-
28
- ```javascript
29
- /**
30
- *
31
- * @param {String} selector ID '#xxxx'
32
- * @param {Object} option - 初始化参数
33
- * @property {Boolean} option.css3d - 模型
34
- * @returns any
35
- */
36
- function useThreeJs(selector, option)
37
- ```
38
-
39
- 使用:
40
- ```javascript
41
- import { useThreeJs } from '@ridp/threejs'
42
-
43
- const { scene, camera, renderer, control, domWidth, domHeight, isReady, addAnimate, frameArea } = useThreeJs("#InsId", )
44
-
45
- ```
46
- ----
47
- 返回变量:
48
- * scene
49
- ```javascript
50
- THREE.Scene
51
-
52
- ```
53
- * camera:
54
- ```javascript
55
- THREE.PerspectiveCamera
56
- ```
57
- * renderer
58
- ```javascript
59
- const renderer = new WebGLRenderer({
60
- antialias: true,
61
- alpha: true,
62
- precision: "mediump",
63
- logarithmicDepthBuffer: true,
64
- })
65
- ```
66
- * control
67
- ```javascript
68
- THREE.OrbitControls
69
- ```
70
- * domWidth
71
- ```javascript
72
- ref(Number)
73
- ```
74
- * domHeight
75
- ```javascript
76
- ref(Number)
77
- ```
78
- * isReady
79
- ```javascript
80
- ref(Boolean)
81
- ```
82
- ------
83
- 返回方法:
84
- * addAnimate
85
- ```javascript
86
- /**
87
- * 循环帧中添加执行函数
88
- * @param {*} animate
89
- */
90
- function addAnimate(animate): void
91
-
92
- ```
93
-
94
- * frameArea
95
- ```javascript
96
- /**物体视窗自动适应
97
- *
98
- * @param {Object3D} model
99
- * @param {Number} scale
100
- * @param {Camera} camera
101
- * @param {OrbitControls} controls
102
- * @param {Vector3} offset
103
- */
104
- function frameArea(model, scale, camera, controls, offset): void
105
- ```
106
- - useRaycaster
107
- - useObb
108
- - useGLTFLoader
109
-
110
- # Instance 实例
111
-
112
-
113
- # Utils 工具
504
+ ### useObb()
505
+
506
+ 有向包围盒(OBB)碰撞检测工具。
507
+
508
+ ```javascript
509
+ import { useObb, obbObjects, intersectColor } from '@ridp/threejs';
510
+
511
+ const {
512
+ resetObbs, // 重置所有 OBB
513
+ initObb, // 初始化模型的 OBB
514
+ initSingleObbItem, // 初始化单个对象的 OBB
515
+ addObbFromArray, // 批量添加 OBB
516
+ getObbObjectByParentUid, // 根据 parentUid 获取 OBB 对象
517
+ checkObbIntersection // 检测碰撞
518
+ } = useObb();
519
+ ```
520
+
521
+ **使用示例:**
522
+ ```javascript
523
+ // 1. 标记需要碰撞检测的模型
524
+ model.userData.needCheck = true;
525
+
526
+ // 2. 初始化 OBB
527
+ initObb('model-uid', model);
528
+
529
+ // 3. 碰撞检测
530
+ const hasCollision = checkObbIntersection(otherObject, obbObjects);
531
+
532
+ // 4. 高亮碰撞对象
533
+ if (hasCollision) {
534
+ object.material.color.set(intersectColor);
535
+ }
536
+ ```
537
+
538
+ ### useBatchGLTFLoader()
539
+
540
+ 批量模型加载器,支持并发控制和进度追踪。
541
+
542
+ ```javascript
543
+ const { loadBatch, loadBatchSequential } = useBatchGLTFLoader();
544
+
545
+ // 并发加载(推荐)
546
+ const results = await loadBatch([
547
+ { url: '/models/part1.glb', version: '1.0.0' },
548
+ { url: '/models/part2.glb', version: '1.0.0' },
549
+ { url: '/models/part3.glb', version: '1.0.0' }
550
+ ], {
551
+ concurrency: 3, // 最大并发数
552
+ onProgress: (progress) => {
553
+ console.log(`进度: ${progress.percent.toFixed(2)}%`);
554
+ }
555
+ });
556
+
557
+ // 顺序加载
558
+ const results = await loadBatchSequential([
559
+ { url: '/models/part1.glb', version: '1.0.0' },
560
+ { url: '/models/part2.glb', version: '1.0.0' }
561
+ ]);
562
+ ```
563
+
564
+ ---
565
+
566
+ ## 工具函数
567
+
568
+ ### 场景辅助工具
569
+
570
+ ```javascript
571
+ import {
572
+ createCameraHelper, // 创建相机辅助器
573
+ createGridHelper, // 创建网格辅助器
574
+ createBox3Helper, // 创建包围盒辅助器
575
+ createOrbitControl, // 创建轨道控制器
576
+ createMapControls, // 创建地图控制器
577
+ createRaycaster, // 创建射线投射器
578
+ createAxesHelper, // 创建坐标轴辅助器
579
+ createArrowHelper, // 创建箭头辅助器
580
+ createStats, // 创建性能监控器
581
+ createTransformControls // 创建变换控制器
582
+ } from '@ridp/threejs';
583
+
584
+ // 示例
585
+ const gridHelper = createGridHelper(100, 10, 0x888888, 0xcccccc);
586
+ scene.add(gridHelper);
587
+
588
+ const stats = createStats();
589
+ document.body.appendChild(stats.dom);
590
+ ```
591
+
592
+ ### initEnvImage()
593
+
594
+ 初始化场景环境贴图。
595
+
596
+ ```javascript
597
+ import { initEnvImage } from '@ridp/threejs';
598
+
599
+ // EXR 格式
600
+ await initEnvImage(scene, '/hdr/studio.exr');
601
+
602
+ // HDR 格式
603
+ await initEnvImage(scene, '/hdr/outdoor.hdr');
604
+
605
+ // PNG/JPG 格式(作为 CubeTexture)
606
+ await initEnvImage(scene, '/textures/env.jpg');
607
+ ```
608
+
609
+ ### modelOptimizer
610
+
611
+ 模型优化工具,用于减少多边形数量和优化材质。
612
+
613
+ ```javascript
614
+ import { modelOptimizer } from '@ridp/threejs';
615
+
616
+ // 获取模型统计信息
617
+ const stats = modelOptimizer.getModelStats(model);
618
+ console.log('网格数量:', stats.meshCount);
619
+ console.log('三角形数:', stats.triangleCount);
620
+ console.log('顶点数:', stats.vertexCount);
621
+ console.log('材质数:', stats.materialCount);
622
+
623
+ // 简化模型几何体
624
+ const optimized = modelOptimizer.simplifyModel(model, {
625
+ ratio: 0.5, // 保留 50% 的面
626
+ mergeMaterials: true, // 合并相同材质
627
+ removeUnused: true // 移除未使用的顶点
628
+ });
629
+
630
+ // 优化材质
631
+ modelOptimizer.optimizeMaterials(model);
632
+ ```
633
+
634
+ ### disposeObject()
635
+
636
+ 深度释放 3D 对象及其子对象的资源。
637
+
638
+ ```javascript
639
+ import { disposeObject } from '@ridp/threejs';
640
+
641
+ // 释放模型资源
642
+ disposeObject(model);
643
+
644
+ // 释放整个场景
645
+ disposeObject(scene);
646
+ ```
647
+
648
+ **清理内容:**
649
+ - 几何体 (geometry)
650
+ - 材质 (material)
651
+ - 纹理 (textures)
652
+ - 子对象递归清理
653
+
654
+ ### sceneRebuilder
655
+
656
+ 渐进式场景重建工具,用于大型模型的分批渲染。
657
+
658
+ ```javascript
659
+ import { ProgressiveSceneBuilder } from '@ridp/threejs';
660
+
661
+ const builder = new ProgressiveSceneBuilder(scene, camera, {
662
+ batchSize: 1000, // 每批添加的网格数
663
+ delay: 16, // 批次间延迟(毫秒)
664
+ onProgress: (progress) => {
665
+ console.log(`构建进度: ${progress.percent}%`);
666
+ }
667
+ });
668
+
669
+ await builder.build(model);
670
+ ```
671
+
672
+ ### objectQuery
673
+
674
+ 3D 对象查询工具,支持按名称、UUID、类型等条件查询。
675
+
676
+ ```javascript
677
+ import { objectQuery } from '@ridp/threejs';
678
+
679
+ // 按名称查询
680
+ const wheels = objectQuery.queryByName(scene, 'wheel_*');
681
+
682
+ // 按 UUID 查询
683
+ const mesh = objectQuery.queryByUuid(scene, 'uuid-123');
684
+
685
+ // 按类型查询
686
+ const allMeshes = objectQuery.queryByType(scene, 'Mesh');
687
+
688
+ // 按条件查询
689
+ const largeObjects = objectQuery.query(scene, (obj) => {
690
+ return obj.scale.x > 10;
691
+ });
692
+ ```
693
+
694
+ ### CSS3D 辅助工具
695
+
696
+ ```javascript
697
+ import { createCSS3DObject, createCSS3DSprite } from '@ridp/threejs';
698
+
699
+ // 创建 CSS3D 对象
700
+ const css3dObject = createCSS3DObject(domElement);
701
+ css3dObject.position.set(0, 10, 0);
702
+ scene.add(css3dObject);
703
+
704
+ // 创建 CSS3D 精灵(始终面向相机)
705
+ const sprite = createCSS3DSprite(domElement, {
706
+ scale: 0.01
707
+ });
708
+ scene.add(sprite);
709
+ ```
710
+
711
+ ### CacheMonitor
712
+
713
+ 缓存性能监控工具。
714
+
715
+ ```javascript
716
+ import { cacheMonitor } from '@ridp/threejs';
717
+
718
+ // 获取缓存统计
719
+ const stats = cacheMonitor.getStats();
720
+ console.log('命中率:', stats.hitRate);
721
+ console.log('平均耗时:', stats.avgLoadTime);
722
+
723
+ // 监听缓存事件
724
+ cacheMonitor.on('hit', (key) => {
725
+ console.log('缓存命中:', key);
726
+ });
727
+
728
+ cacheMonitor.on('miss', (key) => {
729
+ console.log('缓存未命中:', key);
730
+ });
731
+ ```
732
+
733
+ ### RetryHelper
734
+
735
+ 重试机制工具,用于处理不稳定的网络请求。
736
+
737
+ ```javascript
738
+ import { RetryHelper, ModelLoadError } from '@ridp/threejs';
739
+
740
+ const retryHelper = new RetryHelper({
741
+ maxRetries: 3, // 最大重试次数
742
+ baseDelay: 1000, // 基础延迟(毫秒)
743
+ maxDelay: 10000, // 最大延迟(毫秒)
744
+ backoffMultiplier: 2 // 退避乘数
745
+ });
746
+
747
+ try {
748
+ const result = await retryHelper.execute(async () => {
749
+ const response = await fetch('/models/car.glb');
750
+ if (!response.ok) {
751
+ throw new ModelLoadError('加载失败', response.status);
752
+ }
753
+ return response.json();
754
+ });
755
+ } catch (error) {
756
+ console.error('重试失败:', error);
757
+ }
758
+ ```
759
+
760
+ ### PredictiveLoader
761
+
762
+ 预测性加载工具,根据用户行为预加载可能需要的资源。
763
+
764
+ ```javascript
765
+ import { PredictiveLoader } from '@ridp/threejs';
766
+
767
+ const loader = new PredictiveLoader({
768
+ maxPrefetch: 3, // 最大预加载数
769
+ priority: 'recent' // 优先级策略
770
+ });
771
+
772
+ // 预加载模型
773
+ loader.prefetch('/models/next-part.glb', '1.0.0');
774
+
775
+ // 根据用户行为预测
776
+ loader.predict(userBehavior, availableModels);
777
+ ```
778
+
779
+ ---
780
+
781
+ ## 实例类
782
+
783
+ ### IDBCache
784
+
785
+ IndexedDB 缓存管理类。
786
+
787
+ ```javascript
788
+ import { IDBCache } from '@ridp/threejs';
789
+
790
+ const cache = new IDBCache('my-cache-db', 1);
791
+
792
+ // 保存模型到缓存
793
+ await cache.saveModel('model-key', modelData, {
794
+ version: '1.0.0',
795
+ timestamp: Date.now()
796
+ });
797
+
798
+ // 从缓存加载模型
799
+ const data = await cache.loadCachedModel('model-key', '1.0.0');
800
+
801
+ // 清除特定模型缓存
802
+ await cache.deleteModel('model-key');
803
+
804
+ // 清空所有缓存
805
+ await cache.clear();
806
+
807
+ // 获取缓存统计
808
+ const stats = await cache.getStats();
809
+ ```
810
+
811
+ ---
812
+
813
+ ## 完整示例
814
+
815
+ ### Vue 3 组件示例
816
+
817
+ ```vue
818
+ <template>
819
+ <div class="canvas-container" ref="containerRef"></div>
820
+ </template>
821
+
822
+ <script setup>
823
+ import { ref, onMounted, onUnmounted } from 'vue';
824
+ import { ThreeIns, useGLTFLoader, initEnvImage, ViewType } from '@ridp/threejs';
825
+ import * as THREE from 'three';
826
+
827
+ const containerRef = ref(null);
828
+ let threeJsIns = null;
829
+ const { asyncFetch, logCacheReport } = useGLTFLoader();
830
+
831
+ onMounted(async () => {
832
+ // 1. 初始化场景
833
+ threeJsIns = new ThreeIns(containerRef.value, {
834
+ css3d: false,
835
+ renderType: 'loop',
836
+ initListener: true,
837
+ initialFov: 75,
838
+ control: {
839
+ init: true,
840
+ options: {
841
+ maxDistance: Infinity,
842
+ minDistance: 0,
843
+ enablePan: true,
844
+ enableDamping: true,
845
+ dampingFactor: 0.25,
846
+ },
847
+ },
848
+ });
849
+
850
+ // 2. 加载环境贴图
851
+ await initEnvImage(threeJsIns.scene, '/hdr/studio.exr');
852
+
853
+ // 3. 加载模型
854
+ const [err, model] = await asyncFetch('/models/car.glb', '1.0.0', {
855
+ onProgress: (progress) => {
856
+ const percent = (progress.loaded / progress.total * 100).toFixed(2);
857
+ console.log(`加载进度: ${percent}%`);
858
+ }
859
+ });
860
+
861
+ if (model) {
862
+ threeJsIns.scene.add(model);
863
+
864
+ // 4. 设置视角
865
+ threeJsIns.setView(model, ViewType.ISO, {
866
+ scale: 1.0,
867
+ showBox: false,
868
+ animate: true,
869
+ duration: 1000
870
+ });
871
+ }
872
+
873
+ // 5. 查看缓存性能
874
+ logCacheReport();
875
+ });
876
+
877
+ onUnmounted(() => {
878
+ // 清理资源
879
+ if (threeJsIns) {
880
+ threeJsIns.dispose();
881
+ }
882
+ });
883
+ </script>
884
+
885
+ <style scoped>
886
+ .canvas-container {
887
+ width: 100%;
888
+ height: 100vh;
889
+ }
890
+ </style>
891
+ ```
892
+
893
+ ### 多模型加载示例
894
+
895
+ ```javascript
896
+ import { ThreeIns, useBatchGLTFLoader, ViewType } from '@ridp/threejs';
897
+
898
+ const threeJsIns = new ThreeIns('#container', { /* ... */ });
899
+ const { loadBatch } = useBatchGLTFLoader();
900
+
901
+ // 批量加载多个模型
902
+ const models = [
903
+ { url: '/models/floor.glb', version: '1.0.0' },
904
+ { url: '/models/wall.glb', version: '1.0.0' },
905
+ { url: '/models/roof.glb', version: '1.0.0' },
906
+ { url: '/models/furniture.glb', version: '1.0.0' }
907
+ ];
908
+
909
+ const results = await loadBatch(models, {
910
+ concurrency: 2,
911
+ onProgress: (progress) => {
912
+ console.log(`加载进度: ${progress.percent}%`);
913
+ console.log(`已完成: ${progress.loaded}/${progress.total}`);
914
+ }
915
+ });
916
+
917
+ // 添加所有模型到场景
918
+ results.forEach(result => {
919
+ if (result.model) {
920
+ threeJsIns.scene.add(result.model);
921
+ }
922
+ });
923
+
924
+ // 自动适配视角
925
+ const group = new THREE.Group();
926
+ results.forEach(r => r.model && group.add(r.model));
927
+ threeJsIns.setView(group, ViewType.TOP, { scale: 0.8 });
928
+ ```
929
+
930
+ ### 交互式场景示例
931
+
932
+ ```javascript
933
+ import { ThreeIns, useRaycaster, useObb } from '@ridp/threejs';
934
+
935
+ const threeJsIns = new ThreeIns('#container', { /* ... */ });
936
+ const { getIntersects } = useRaycaster('app');
937
+ const { initObb, checkObbIntersection } = useObb();
938
+
939
+ // 加载模型后初始化碰撞检测
940
+ model.userData.needCheck = true;
941
+ initObb('model-1', model);
942
+
943
+ // 点击事件
944
+ threeJsIns.renderer.domElement.addEventListener('click', (event) => {
945
+ const { intersects } = getIntersects(
946
+ event,
947
+ threeJsIns.renderer.domElement,
948
+ threeJsIns.camera,
949
+ [model]
950
+ );
951
+
952
+ if (intersects.length > 0) {
953
+ const object = intersects[0].object;
954
+ console.log('点击了:', object.name);
955
+
956
+ // 检测碰撞
957
+ const hasCollision = checkObbIntersection(object, obbObjects);
958
+ if (hasCollision) {
959
+ object.material.color.set(0xff0000);
960
+ }
961
+ }
962
+ });
963
+ ```
964
+
965
+ ---
966
+
967
+ ## 性能优化建议
968
+
969
+ ### 1. 使用模型缓存和自动优化
970
+
971
+ 对于重复访问的模型,启用 IndexedDB 缓存可以显著提升加载速度。同时可以启用材质优化和几何体简化来提升渲染性能:
972
+
973
+ ```javascript
974
+ // 首次加载: ~2s (含优化)
975
+ await asyncFetch('/models/large-scene.glb', '1.0.0', {
976
+ optimizeMaterials: true, // 合并相同材质,减少 draw calls
977
+ simplifyGeometry: true, // 简化几何体,减少三角形数
978
+ simplifyRatio: 0.5 // 保留 50% 的面
979
+ });
980
+
981
+ // 二次加载: ~200ms (从缓存读取,仍需优化)
982
+ await asyncFetch('/models/large-scene.glb', '1.0.0', {
983
+ optimizeMaterials: true,
984
+ simplifyGeometry: true,
985
+ simplifyRatio: 0.5
986
+ });
987
+ ```
988
+
989
+ **优化选项说明:**
990
+
991
+ - **`optimizeMaterials`** (材质优化)
992
+ - 自动合并相同属性的材质
993
+ - 减少材质切换次数
994
+ - 降低 GPU draw calls
995
+ - 适用场景: 模型中有很多相似材质的对象
996
+
997
+ - **`simplifyGeometry`** (几何体简化)
998
+ - 减少模型的三角形数量
999
+ - 显著提升渲染性能
1000
+ - 适合大型场景或低性能设备
1001
+ - 可配置简化比例和最小面数阈值
1002
+
1003
+ - **`simplifyRatio`** (简化比例)
1004
+ - `0.8` - 保留 80% 的面(轻度简化)
1005
+ - `0.5` - 保留 50% 的面(中度简化,推荐)
1006
+ - `0.3` - 保留 30% 的面(重度简化,适合远景模型)
1007
+
1008
+ **性能对比:**
1009
+ ```
1010
+ 未优化模型: 50万三角形, 120个材质
1011
+ 优化后: 25万三角形 (50%减少), 45个材质 (62%减少)
1012
+ FPS提升: 30 FPS -> 55 FPS (83%提升)
1013
+ ```
1014
+
1015
+ ### 2. 批量加载优化
1016
+
1017
+ 使用批量加载时,合理控制并发数:
1018
+
1019
+ ```javascript
1020
+ // 推荐: 根据设备性能动态调整
1021
+ const concurrency = navigator.hardwareConcurrency || 4;
1022
+ await loadBatch(models, { concurrency });
1023
+ ```
1024
+
1025
+ ### 3. 手动模型优化
1026
+
1027
+ 如果需要在加载后手动优化模型:
1028
+
1029
+ ```javascript
1030
+ import { modelOptimizer } from '@ridp/threejs';
1031
+
1032
+ // 材质优化
1033
+ modelOptimizer.optimizeMaterials(model);
1034
+
1035
+ // 几何体简化
1036
+ modelOptimizer.simplifyModel(model, 0.5, {
1037
+ minFaceCount: 500,
1038
+ preserveUVs: true
1039
+ });
1040
+
1041
+ // 查看优化效果
1042
+ const stats = modelOptimizer.getModelStats(model);
1043
+ console.log('优化后统计:', stats);
1044
+ ```
1045
+
1046
+ ### 4. 渐进式渲染
1047
+
1048
+ 对于超大场景,使用渐进式渲染避免长时间卡顿:
1049
+
1050
+ ```javascript
1051
+ import { ProgressiveSceneBuilder } from '@ridp/threejs';
1052
+
1053
+ const builder = new ProgressiveSceneBuilder(scene, camera, {
1054
+ batchSize: 1000,
1055
+ delay: 16
1056
+ });
1057
+ await builder.build(model);
1058
+ ```
1059
+
1060
+ ### 5. 合理使用阴影
1061
+
1062
+ 阴影是性能杀手,仅在必要时启用:
1063
+
1064
+ ```javascript
1065
+ renderer.shadowMap.enabled = true; // 启用阴影
1066
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 软阴影
1067
+
1068
+ // 仅重要对象投射阴影
1069
+ importantObject.castShadow = true;
1070
+ otherObjects.castShadow = false;
1071
+ ```
1072
+
1073
+ ---
1074
+
1075
+ ## 常见问题
1076
+
1077
+ ### Q: 如何处理模型加载失败?
1078
+
1079
+ A: 使用 `asyncFetch` 的重试机制:
1080
+
1081
+ ```javascript
1082
+ const [err, model] = await asyncFetch(url, version, {
1083
+ retryCount: 5,
1084
+ timeout: 60000
1085
+ });
1086
+
1087
+ if (err || !model) {
1088
+ console.error('模型加载失败:', err);
1089
+ // 显示错误提示或加载备用模型
1090
+ }
1091
+ ```
1092
+
1093
+ ### Q: 如何清理模型缓存?
1094
+
1095
+ A: 使用 `clearCache` 方法:
1096
+
1097
+ ```javascript
1098
+ const { clearCache } = useGLTFLoader();
1099
+ await clearCache(); // 清空所有缓存
1100
+
1101
+ // 或使用 IDBCache 实例
1102
+ import { IDBCache } from '@ridp/threejs';
1103
+ const cache = new IDBCache();
1104
+ await cache.clear();
1105
+ ```
1106
+
1107
+ ### Q: 模型显示太小或太大?
1108
+
1109
+ A: 使用 `setView` 的 `scale` 参数:
1110
+
1111
+ ```javascript
1112
+ // 模型太小 - 放大
1113
+ threeJsIns.setView(model, ViewType.ISO, { scale: 2.0 });
1114
+
1115
+ // 模型太大 - 缩小
1116
+ threeJsIns.setView(model, ViewType.ISO, { scale: 0.5 });
1117
+ ```
1118
+
1119
+ > **注意**: 旧方法 `frameArea()` 已弃用,请使用 `setView()` 代替。
1120
+
1121
+ ### Q: 如何监听窗口大小变化?
1122
+
1123
+ A: 如果 `initListener: true`,会自动监听。否则手动监听:
1124
+
1125
+ ```javascript
1126
+ window.addEventListener('resize', () => {
1127
+ threeJsIns.updateCameraFOV();
1128
+ threeJsIns.camera.updateProjectionMatrix();
1129
+ threeJsIns.renderer.setSize(
1130
+ threeJsIns.domElement.clientWidth,
1131
+ threeJsIns.domElement.clientHeight
1132
+ );
1133
+ });
1134
+ ```
1135
+
1136
+ ### Q: 如何导出场景截图?
1137
+
1138
+ A: 使用 `renderer.domElement.toDataURL()`:
1139
+
1140
+ ```javascript
1141
+ function captureScreenshot() {
1142
+ threeJsIns.render(); // 确保最新帧已渲染
1143
+ const dataURL = threeJsIns.renderer.domElement.toDataURL('image/png');
1144
+ const link = document.createElement('a');
1145
+ link.download = 'screenshot.png';
1146
+ link.href = dataURL;
1147
+ link.click();
1148
+ }
1149
+ ```
1150
+
1151
+ ---
1152
+
1153
+ ## 更新日志
1154
+
1155
+ ### v1.3.3 (2025-01-06)
1156
+
1157
+ - 🎯 优化 `setView` 方法的 scale 参数计算逻辑
1158
+ - 🔧 修复水平 FOV 超过 180° 导致的负数距离问题
1159
+ - ✨ 移除 FRONT/BACK/BOTTOM 视角,保留 TOP/RIGHT/LEFT/ISO
1160
+ - ⚡ 优化 OrbitControls 距离限制的自动处理
1161
+ - 🐛 修复相机 near/far 平面裁剪问题
1162
+ - ⚠️ **弃用**: `useThreeJs()` Hook - 推荐使用 **ThreeIns 类** (功能更完整,不限框架)
1163
+ - ⚠️ **弃用**: `frameArea()` 方法 - 推荐使用 **setView() 方法** (支持多视角)
1164
+ - ✨ **新增**: `asyncFetch` 支持自动材质优化 (`optimizeMaterials`)
1165
+ - ✨ **新增**: `asyncFetch` 支持几何体简化 (`simplifyGeometry`)
1166
+ - ✨ **新增**: `modelOptimizer.simplifyModel()` 方法 - 整体模型简化
1167
+ - 📦 **增强**: 完整的模型优化工作流,支持加载时自动优化
1168
+
1169
+ ### v1.3.2 (2025-01-05)
1170
+
1171
+ - ✨ 新增 Web Worker 支持,优化 GLTF 解析性能
1172
+ - 📦 增强模型缓存机制
1173
+ - 🎨 新增 `modelOptimizer` 工具
1174
+ - ⚡ 性能优化:减少重复计算
1175
+
1176
+ ### v1.3.0 (2024-12-XX)
1177
+
1178
+ - ✨ 新增 `useBatchGLTFLoader` 批量加载 Hook
1179
+ - 🎯 新增 `setView` 方法,支持多视角切换
1180
+ - 📊 新增 `CacheMonitor` 缓存监控工具
1181
+ - 🔧 新增 `RetryHelper` 重试机制
1182
+
1183
+ 完整更新日志请查看 [CHANGELOG.md](./CHANGELOG.md)
1184
+
1185
+ ---
1186
+
1187
+ ## 许可证
1188
+
1189
+ MIT License
1190
+
1191
+ ---
1192
+
1193
+ ## 支持
1194
+
1195
+ - 📧 Email: support@example.com
1196
+ - 🐛 Issues: [GitHub Issues](https://github.com/example/threejs/issues)
1197
+ - 📖 文档: [在线文档](https://example.com/docs)
1198
+
1199
+ ---
1200
+
1201
+ ## 相关资源
1202
+
1203
+ - [Three.js 官方文档](https://threejs.org/docs/)
1204
+ - [Three.js 示例](https://threejs.org/examples/)
1205
+ - [GLTF 文件格式规范](https://github.com/KhronosGroup/glTF)