@ridp/threejs 1.3.3 → 1.4.0
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/dist/ImageLoader-DXx88iwh.js +1444 -0
- package/dist/ImageLoader-DzG6sgbB.cjs +24 -0
- package/dist/PredictiveLoader--_3bEnce.js +3738 -0
- package/dist/PredictiveLoader-CpRi-ULa.cjs +2 -0
- package/dist/assets/gltfParser.worker-Bqz8BBJx.js +6 -0
- package/dist/assets/gltfParserOptimized.worker-DfipxPjm.js +6 -0
- package/dist/hooks.cjs +1 -1
- package/dist/hooks.js +7 -5
- package/dist/modelOptimizer-A0Cs6f9e.cjs +1 -0
- package/dist/modelOptimizer-BRPnM2RH.js +179 -0
- package/dist/threejs.cjs +3 -1
- package/dist/threejs.js +334 -118
- package/dist/useBatchGLTFLoader-CDNvTMtt.cjs +5 -0
- package/dist/useBatchGLTFLoader-Dg-xau9i.js +493 -0
- package/dist/utils.cjs +1 -1
- package/dist/utils.js +32 -20
- package/package.json +1 -1
- package/readme.md +1186 -94
- package/dist/ImageLoader-DwvBWW6h.cjs +0 -5
- package/dist/ImageLoader-xzOH4Owt.js +0 -860
- package/dist/modelSerialize-B8gu_KnK.cjs +0 -15
- package/dist/modelSerialize-DWUt6OcZ.js +0 -276
- package/dist/useGLTFLoader-D01iU8lB.cjs +0 -2
- package/dist/useGLTFLoader-MhUFIZp1.js +0 -2510
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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)
|