@chocozhang/three-model-render 1.0.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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +609 -0
  3. package/dist/camera/index.d.ts +133 -0
  4. package/dist/camera/index.js +291 -0
  5. package/dist/camera/index.js.map +1 -0
  6. package/dist/camera/index.mjs +265 -0
  7. package/dist/camera/index.mjs.map +1 -0
  8. package/dist/core/index.d.ts +102 -0
  9. package/dist/core/index.js +455 -0
  10. package/dist/core/index.js.map +1 -0
  11. package/dist/core/index.mjs +432 -0
  12. package/dist/core/index.mjs.map +1 -0
  13. package/dist/effect/index.d.ts +214 -0
  14. package/dist/effect/index.js +749 -0
  15. package/dist/effect/index.js.map +1 -0
  16. package/dist/effect/index.mjs +728 -0
  17. package/dist/effect/index.mjs.map +1 -0
  18. package/dist/index.d.ts +852 -0
  19. package/dist/index.js +3268 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/index.mjs +3223 -0
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/interaction/index.d.ts +160 -0
  24. package/dist/interaction/index.js +661 -0
  25. package/dist/interaction/index.js.map +1 -0
  26. package/dist/interaction/index.mjs +637 -0
  27. package/dist/interaction/index.mjs.map +1 -0
  28. package/dist/loader/index.d.ts +175 -0
  29. package/dist/loader/index.js +786 -0
  30. package/dist/loader/index.js.map +1 -0
  31. package/dist/loader/index.mjs +758 -0
  32. package/dist/loader/index.mjs.map +1 -0
  33. package/dist/setup/index.d.ts +47 -0
  34. package/dist/setup/index.js +199 -0
  35. package/dist/setup/index.js.map +1 -0
  36. package/dist/setup/index.mjs +178 -0
  37. package/dist/setup/index.mjs.map +1 -0
  38. package/dist/ui/index.d.ts +36 -0
  39. package/dist/ui/index.js +292 -0
  40. package/dist/ui/index.js.map +1 -0
  41. package/dist/ui/index.mjs +271 -0
  42. package/dist/ui/index.mjs.map +1 -0
  43. package/package.json +98 -0
@@ -0,0 +1,432 @@
1
+ import * as THREE from 'three';
2
+ import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
3
+ import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
4
+ import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass';
5
+ import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader';
6
+ import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass';
7
+
8
+ /**
9
+ * 给子模型添加头顶标签(支持 Mesh 和 Group)- 优化版
10
+ *
11
+ * ✨ 性能优化:
12
+ * - 缓存包围盒,避免每帧重复计算
13
+ * - 支持暂停/恢复更新
14
+ * - 可配置更新间隔,降低 CPU 占用
15
+ * - 只在可见时更新,隐藏时自动暂停
16
+ *
17
+ * @param camera THREE.Camera - 场景摄像机
18
+ * @param renderer THREE.WebGLRenderer - 渲染器,用于屏幕尺寸
19
+ * @param parentModel THREE.Object3D - FBX 根节点或 Group
20
+ * @param modelLabelsMap Record<string,string> - 模型 name → 标签文字 映射表
21
+ * @param options LabelOptions - 可选标签样式配置
22
+ * @returns LabelManager - 包含 pause/resume/dispose 的管理接口
23
+ */
24
+ function addChildModelLabels(camera, renderer, parentModel, modelLabelsMap, options) {
25
+ // 防御性检查:确保 parentModel 已加载
26
+ if (!parentModel || typeof parentModel.traverse !== 'function') {
27
+ console.error('parentModel 无效,请确保 FBX 模型已加载完成');
28
+ return {
29
+ pause: () => { },
30
+ resume: () => { },
31
+ dispose: () => { },
32
+ isRunning: () => false
33
+ };
34
+ }
35
+ // 配置项
36
+ const enableCache = (options === null || options === void 0 ? void 0 : options.enableCache) !== false;
37
+ const updateInterval = (options === null || options === void 0 ? void 0 : options.updateInterval) || 0;
38
+ // 创建标签容器,绝对定位,放在 body 中
39
+ const container = document.createElement('div');
40
+ container.style.position = 'absolute';
41
+ container.style.top = '0';
42
+ container.style.left = '0';
43
+ container.style.pointerEvents = 'none'; // 避免阻挡鼠标事件
44
+ container.style.zIndex = '1000';
45
+ document.body.appendChild(container);
46
+ const labels = [];
47
+ // 状态管理
48
+ let rafId = null;
49
+ let isPaused = false;
50
+ let lastUpdateTime = 0;
51
+ // 遍历所有子模型
52
+ parentModel.traverse((child) => {
53
+ var _a;
54
+ // 只处理 Mesh 或 Group
55
+ if ((child.isMesh || child.type === 'Group')) {
56
+ // 动态匹配 name,防止 undefined
57
+ const labelText = (_a = Object.entries(modelLabelsMap).find(([key]) => child.name.includes(key))) === null || _a === void 0 ? void 0 : _a[1];
58
+ if (!labelText)
59
+ return; // 没有匹配标签则跳过
60
+ // 创建 DOM 标签
61
+ const el = document.createElement('div');
62
+ el.innerText = labelText;
63
+ // 样式直接在 JS 中定义,可通过 options 覆盖
64
+ el.style.position = 'absolute';
65
+ el.style.color = (options === null || options === void 0 ? void 0 : options.color) || '#fff';
66
+ el.style.background = (options === null || options === void 0 ? void 0 : options.background) || 'rgba(0,0,0,0.6)';
67
+ el.style.padding = (options === null || options === void 0 ? void 0 : options.padding) || '4px 8px';
68
+ el.style.borderRadius = (options === null || options === void 0 ? void 0 : options.borderRadius) || '4px';
69
+ el.style.fontSize = (options === null || options === void 0 ? void 0 : options.fontSize) || '14px';
70
+ el.style.transform = 'translate(-50%, -100%)'; // 让标签在模型正上方
71
+ el.style.whiteSpace = 'nowrap';
72
+ el.style.pointerEvents = 'none';
73
+ el.style.transition = 'opacity 0.2s ease';
74
+ // 加入容器
75
+ container.appendChild(el);
76
+ // 初始化缓存
77
+ const cachedBox = new THREE.Box3().setFromObject(child);
78
+ const center = new THREE.Vector3();
79
+ cachedBox.getCenter(center);
80
+ const cachedTopPos = new THREE.Vector3(center.x, cachedBox.max.y, center.z);
81
+ labels.push({
82
+ object: child,
83
+ el,
84
+ cachedBox,
85
+ cachedTopPos,
86
+ needsUpdate: true
87
+ });
88
+ }
89
+ });
90
+ /**
91
+ * 更新缓存的包围盒(仅在模型变换时调用)
92
+ */
93
+ const updateCache = (labelData) => {
94
+ labelData.cachedBox.setFromObject(labelData.object);
95
+ const center = new THREE.Vector3();
96
+ labelData.cachedBox.getCenter(center);
97
+ labelData.cachedTopPos.set(center.x, labelData.cachedBox.max.y, center.z);
98
+ labelData.needsUpdate = false;
99
+ };
100
+ /**
101
+ * 获取对象顶部世界坐标(使用缓存)
102
+ */
103
+ const getObjectTopPosition = (labelData) => {
104
+ if (enableCache) {
105
+ // 检查对象是否发生变换
106
+ if (labelData.needsUpdate || labelData.object.matrixWorldNeedsUpdate) {
107
+ updateCache(labelData);
108
+ }
109
+ return labelData.cachedTopPos;
110
+ }
111
+ else {
112
+ // 不使用缓存,每次都重新计算
113
+ const box = new THREE.Box3().setFromObject(labelData.object);
114
+ const center = new THREE.Vector3();
115
+ box.getCenter(center);
116
+ return new THREE.Vector3(center.x, box.max.y, center.z);
117
+ }
118
+ };
119
+ /**
120
+ * 更新标签位置函数
121
+ */
122
+ function updateLabels(timestamp = 0) {
123
+ // 检查是否暂停
124
+ if (isPaused) {
125
+ rafId = null;
126
+ return;
127
+ }
128
+ // 检查更新间隔
129
+ if (updateInterval > 0 && timestamp - lastUpdateTime < updateInterval) {
130
+ rafId = requestAnimationFrame(updateLabels);
131
+ return;
132
+ }
133
+ lastUpdateTime = timestamp;
134
+ const width = renderer.domElement.clientWidth;
135
+ const height = renderer.domElement.clientHeight;
136
+ labels.forEach((labelData) => {
137
+ const { el } = labelData;
138
+ const pos = getObjectTopPosition(labelData); // 使用缓存的顶部坐标
139
+ pos.project(camera); // 转到屏幕坐标
140
+ const x = (pos.x * 0.5 + 0.5) * width; // 屏幕 X
141
+ const y = (-(pos.y * 0.5) + 0.5) * height; // 屏幕 Y
142
+ // 控制标签显示/隐藏(摄像机后方隐藏)
143
+ const isVisible = pos.z < 1;
144
+ el.style.opacity = isVisible ? '1' : '0';
145
+ el.style.display = isVisible ? 'block' : 'none';
146
+ el.style.transform = `translate(-50%, -100%) translate(${x}px, ${y}px)`; // 屏幕位置
147
+ });
148
+ rafId = requestAnimationFrame(updateLabels); // 循环更新
149
+ }
150
+ // 启动更新
151
+ updateLabels();
152
+ /**
153
+ * 暂停更新
154
+ */
155
+ const pause = () => {
156
+ isPaused = true;
157
+ if (rafId !== null) {
158
+ cancelAnimationFrame(rafId);
159
+ rafId = null;
160
+ }
161
+ };
162
+ /**
163
+ * 恢复更新
164
+ */
165
+ const resume = () => {
166
+ if (!isPaused)
167
+ return;
168
+ isPaused = false;
169
+ updateLabels();
170
+ };
171
+ /**
172
+ * 检查是否正在运行
173
+ */
174
+ const isRunning = () => !isPaused;
175
+ /**
176
+ * 清理函数:卸载所有 DOM 标签,取消动画,避免内存泄漏
177
+ */
178
+ const dispose = () => {
179
+ pause();
180
+ labels.forEach(({ el }) => {
181
+ if (container.contains(el)) {
182
+ container.removeChild(el);
183
+ }
184
+ });
185
+ if (document.body.contains(container)) {
186
+ document.body.removeChild(container);
187
+ }
188
+ labels.length = 0;
189
+ };
190
+ return {
191
+ pause,
192
+ resume,
193
+ dispose,
194
+ isRunning
195
+ };
196
+ }
197
+
198
+ // src/utils/hoverBreathEffectByNameSingleton.ts
199
+ /**
200
+ * 创建单例高亮器 —— 推荐在 mounted 时创建一次
201
+ * 返回 { updateHighlightNames, dispose, getHoveredName } 接口
202
+ *
203
+ * ✨ 性能优化:
204
+ * - 无 hover 对象时自动暂停动画
205
+ * - mousemove 节流处理,避免过度计算
206
+ * - 使用 passive 事件监听器提升滚动性能
207
+ */
208
+ function enableHoverBreath(opts) {
209
+ const { camera, scene, renderer, outlinePass, highlightNames = null, minStrength = 2, maxStrength = 5, speed = 4, throttleDelay = 16, // 默认约 60fps
210
+ } = opts;
211
+ const raycaster = new THREE.Raycaster();
212
+ const mouse = new THREE.Vector2();
213
+ let hovered = null;
214
+ let time = 0;
215
+ let animationId = null;
216
+ // highlightSet: null 表示 all; empty Set 表示 none
217
+ let highlightSet = highlightNames === null ? null : new Set(highlightNames);
218
+ // 节流相关
219
+ let lastMoveTime = 0;
220
+ let rafPending = false;
221
+ function setHighlightNames(names) {
222
+ highlightSet = names === null ? null : new Set(names);
223
+ // 如果当前 hovered 不在新名单中,及时清理 selection
224
+ if (hovered && highlightSet && !highlightSet.has(hovered.name)) {
225
+ hovered = null;
226
+ outlinePass.selectedObjects = [];
227
+ // 暂停动画
228
+ if (animationId !== null) {
229
+ cancelAnimationFrame(animationId);
230
+ animationId = null;
231
+ }
232
+ }
233
+ }
234
+ /**
235
+ * 节流版本的 mousemove 处理
236
+ */
237
+ function onMouseMove(ev) {
238
+ const now = performance.now();
239
+ // 节流:如果距上次处理时间小于阈值,跳过
240
+ if (now - lastMoveTime < throttleDelay) {
241
+ // 使用 RAF 延迟处理,避免丢失最后一次事件
242
+ if (!rafPending) {
243
+ rafPending = true;
244
+ requestAnimationFrame(() => {
245
+ rafPending = false;
246
+ processMouseMove(ev);
247
+ });
248
+ }
249
+ return;
250
+ }
251
+ lastMoveTime = now;
252
+ processMouseMove(ev);
253
+ }
254
+ /**
255
+ * 实际的 mousemove 逻辑
256
+ */
257
+ function processMouseMove(ev) {
258
+ const rect = renderer.domElement.getBoundingClientRect();
259
+ mouse.x = ((ev.clientX - rect.left) / rect.width) * 2 - 1;
260
+ mouse.y = -((ev.clientY - rect.top) / rect.height) * 2 + 1;
261
+ raycaster.setFromCamera(mouse, camera);
262
+ // 深度检测 scene 的所有子对象(true)
263
+ const intersects = raycaster.intersectObjects(scene.children, true);
264
+ if (intersects.length > 0) {
265
+ const obj = intersects[0].object;
266
+ // 判断是否允许被高亮
267
+ const allowed = highlightSet === null ? true : highlightSet.has(obj.name);
268
+ if (allowed) {
269
+ if (hovered !== obj) {
270
+ hovered = obj;
271
+ outlinePass.selectedObjects = [obj];
272
+ // 启动动画(如果未运行)
273
+ if (animationId === null) {
274
+ animate();
275
+ }
276
+ }
277
+ }
278
+ else {
279
+ if (hovered !== null) {
280
+ hovered = null;
281
+ outlinePass.selectedObjects = [];
282
+ // 停止动画
283
+ if (animationId !== null) {
284
+ cancelAnimationFrame(animationId);
285
+ animationId = null;
286
+ }
287
+ }
288
+ }
289
+ }
290
+ else {
291
+ if (hovered !== null) {
292
+ hovered = null;
293
+ outlinePass.selectedObjects = [];
294
+ // 停止动画
295
+ if (animationId !== null) {
296
+ cancelAnimationFrame(animationId);
297
+ animationId = null;
298
+ }
299
+ }
300
+ }
301
+ }
302
+ /**
303
+ * 动画循环 - 只在有 hovered 对象时运行
304
+ */
305
+ function animate() {
306
+ // 如果没有 hovered 对象,停止动画
307
+ if (!hovered) {
308
+ animationId = null;
309
+ return;
310
+ }
311
+ animationId = requestAnimationFrame(animate);
312
+ time += speed * 0.02;
313
+ const strength = minStrength + ((Math.sin(time) + 1) / 2) * (maxStrength - minStrength);
314
+ outlinePass.edgeStrength = strength;
315
+ }
316
+ // 启动(只调用一次)
317
+ // 使用 passive 提升滚动性能
318
+ renderer.domElement.addEventListener('mousemove', onMouseMove, { passive: true });
319
+ // 注意:不在这里启动 animate,等有 hover 对象时再启动
320
+ // refresh: 如果你在某些情况下需要强制清理 selectedObjects
321
+ function refreshSelection() {
322
+ if (hovered && highlightSet && !highlightSet.has(hovered.name)) {
323
+ hovered = null;
324
+ outlinePass.selectedObjects = [];
325
+ if (animationId !== null) {
326
+ cancelAnimationFrame(animationId);
327
+ animationId = null;
328
+ }
329
+ }
330
+ }
331
+ function getHoveredName() {
332
+ return hovered ? hovered.name : null;
333
+ }
334
+ function dispose() {
335
+ renderer.domElement.removeEventListener('mousemove', onMouseMove);
336
+ if (animationId) {
337
+ cancelAnimationFrame(animationId);
338
+ animationId = null;
339
+ }
340
+ outlinePass.selectedObjects = [];
341
+ // 清空引用
342
+ hovered = null;
343
+ highlightSet = null;
344
+ }
345
+ return {
346
+ updateHighlightNames: setHighlightNames,
347
+ dispose,
348
+ refreshSelection,
349
+ getHoveredName,
350
+ };
351
+ }
352
+
353
+ /**
354
+ * 初始化描边相关信息(包含 OutlinePass)- 优化版
355
+ *
356
+ * ✨ 功能增强:
357
+ * - 支持窗口 resize 自动更新
358
+ * - 可配置分辨率缩放提升性能
359
+ * - 完善的资源释放管理
360
+ *
361
+ * @param renderer THREE.WebGLRenderer
362
+ * @param scene THREE.Scene
363
+ * @param camera THREE.Camera
364
+ * @param options PostProcessingOptions - 可选配置
365
+ * @returns PostProcessingManager - 包含 composer/outlinePass/resize/dispose 的管理接口
366
+ */
367
+ function initPostProcessing(renderer, scene, camera, options = {}) {
368
+ // 默认配置
369
+ const { edgeStrength = 4, edgeGlow = 1, edgeThickness = 2, visibleEdgeColor = '#ffee00', hiddenEdgeColor = '#000000', resolutionScale = 1.0 } = options;
370
+ // 获取渲染器实际尺寸
371
+ const getSize = () => {
372
+ const width = renderer.domElement.clientWidth;
373
+ const height = renderer.domElement.clientHeight;
374
+ return {
375
+ width: Math.floor(width * resolutionScale),
376
+ height: Math.floor(height * resolutionScale)
377
+ };
378
+ };
379
+ const size = getSize();
380
+ // 创建 EffectComposer
381
+ const composer = new EffectComposer(renderer);
382
+ // 基础 RenderPass
383
+ const renderPass = new RenderPass(scene, camera);
384
+ composer.addPass(renderPass);
385
+ // OutlinePass 用于模型描边
386
+ const outlinePass = new OutlinePass(new THREE.Vector2(size.width, size.height), scene, camera);
387
+ outlinePass.edgeStrength = edgeStrength;
388
+ outlinePass.edgeGlow = edgeGlow;
389
+ outlinePass.edgeThickness = edgeThickness;
390
+ outlinePass.visibleEdgeColor.set(visibleEdgeColor);
391
+ outlinePass.hiddenEdgeColor.set(hiddenEdgeColor);
392
+ composer.addPass(outlinePass);
393
+ // Gamma 校正
394
+ const gammaPass = new ShaderPass(GammaCorrectionShader);
395
+ composer.addPass(gammaPass);
396
+ /**
397
+ * resize 处理函数
398
+ * @param width 可选宽度,不传则使用 renderer.domElement 的实际宽度
399
+ * @param height 可选高度,不传则使用 renderer.domElement 的实际高度
400
+ */
401
+ const resize = (width, height) => {
402
+ const actualSize = width !== undefined && height !== undefined
403
+ ? { width: Math.floor(width * resolutionScale), height: Math.floor(height * resolutionScale) }
404
+ : getSize();
405
+ // 更新 composer 尺寸
406
+ composer.setSize(actualSize.width, actualSize.height);
407
+ // 更新 outlinePass 分辨率
408
+ outlinePass.resolution.set(actualSize.width, actualSize.height);
409
+ };
410
+ /**
411
+ * 释放资源
412
+ */
413
+ const dispose = () => {
414
+ // 释放所有 passes
415
+ composer.passes.forEach(pass => {
416
+ if (pass.dispose) {
417
+ pass.dispose();
418
+ }
419
+ });
420
+ // 清空 passes 数组
421
+ composer.passes.length = 0;
422
+ };
423
+ return {
424
+ composer,
425
+ outlinePass,
426
+ resize,
427
+ dispose
428
+ };
429
+ }
430
+
431
+ export { addChildModelLabels, enableHoverBreath, initPostProcessing };
432
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../../src/core/labelManager.ts","../../src/core/hoverEffect.ts","../../src/core/postProcessing.ts"],"sourcesContent":[null,null,null],"names":[],"mappings":"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"}
@@ -0,0 +1,214 @@
1
+ import * as THREE from 'three';
2
+
3
+ /**
4
+ * GroupExploder - 基于 Three.js 的模型爆炸效果工具(支持 Vue3 + TS)
5
+ * ----------------------------------------------------------------------
6
+ * 该工具用于对指定 Mesh 的集合进行“爆炸 / 还原”动画:
7
+ * - 仅初始化一次(onMounted)
8
+ * - 支持动态切换模型并自动还原上一个模型的爆炸状态
9
+ * - 支持多种排列模式(ring / spiral / grid / radial)
10
+ * - 支持非爆炸对象自动透明化(dimOthers)
11
+ * - 支持摄像机自动前置定位到最佳观察点
12
+ * - 所有动画均采用原生 requestAnimationFrame 实现
13
+ *
14
+ * ----------------------------------------------------------------------
15
+ * 🔧 构造参数
16
+ * ----------------------------------------------------------------------
17
+ * @param scene Three.js 场景实例
18
+ * @param camera Three.js 相机(一般为 PerspectiveCamera)
19
+ * @param controls OrbitControls 控件实例(必须绑定 camera)
20
+ *
21
+ * ----------------------------------------------------------------------
22
+ * 🔥 爆炸参数 ExplodeOptions
23
+ * ----------------------------------------------------------------------
24
+ * 所有参数均可在 explode() 调用时指定,也可设置默认值。
25
+ *
26
+ * type ArrangeMode = 'ring' | 'spiral' | 'grid' | 'radial'
27
+ *
28
+ * @param mode?: ArrangeMode
29
+ * 爆炸排列方式:
30
+ * - 'ring' 环形排列(默认)
31
+ * - 'spiral' 螺旋上升排列
32
+ * - 'grid' 平面网格排列(规则整齐)
33
+ * - 'radial' 从中心点向外扩散
34
+ *
35
+ * @param spacing?: number
36
+ * 相邻爆炸对象之间的间距(默认:2.5)
37
+ *
38
+ * @param duration?: number
39
+ * 爆炸动画时长(ms),原生 rAF 完成(默认:1000)
40
+ *
41
+ * @param lift?: number
42
+ * 爆炸对象整体抬升的高度因子,用于让爆炸看起来更立体(默认:0.6)
43
+ *
44
+ * @param cameraPadding?: number
45
+ * 摄像机贴合爆炸后包围球时的额外安全距离(默认:1.2)
46
+ *
47
+ * @param autoRestorePrev?: boolean
48
+ * 当切换模型时,是否自动 restore 上一个模型的爆炸元素(默认:true)
49
+ *
50
+ * @param dimOthers?: { enabled: boolean; opacity?: number }
51
+ * 非爆炸对象透明化配置:
52
+ * - enabled: true 开启
53
+ * - opacity: number 指定非爆炸对象透明度(默认:0.15)
54
+ *
55
+ * @param debug?: boolean
56
+ * 是否开启调试日志,输出所有内部状态(默认 false)
57
+ *
58
+ *
59
+ * ----------------------------------------------------------------------
60
+ * 📌 方法说明
61
+ * ----------------------------------------------------------------------
62
+ *
63
+ * ◆ setMeshes(meshSet: Set<Mesh>, contextId?: string)
64
+ * 设置当前模型的爆炸 Mesh 集合:
65
+ * - 会记录 Mesh 的初始 transform
66
+ * - 根据 autoRestorePrev 自动还原上次爆炸
67
+ * - 第二个参数 contextId 可选,用于区分业务场景
68
+ *
69
+ *
70
+ * ◆ explode(options?: ExplodeOptions)
71
+ * 对当前 meshSet 执行爆炸动画:
72
+ * - 根据 mode 生成爆炸布局
73
+ * - 相机先自动飞向最佳观察点
74
+ * - 执行 mesh 位移动画
75
+ * - 按需将非爆炸模型透明化
76
+ *
77
+ *
78
+ * ◆ restore(duration?: number)
79
+ * 还原所有爆炸 Mesh 到爆炸前的 transform:
80
+ * - 支持平滑动画
81
+ * - 自动取消透明化
82
+ *
83
+ *
84
+ * ◆ dispose()
85
+ * 移除事件监听、取消动画、清理引用(在组件销毁时调用)
86
+ *
87
+ *
88
+ * ----------------------------------------------------------------------
89
+ * 🎨 排列模式说明
90
+ * ----------------------------------------------------------------------
91
+ *
92
+ * 1. Ring(环形)
93
+ * - 按圆均匀分布
94
+ * - spacing 控制半径
95
+ * - lift 控制整体抬起高度
96
+ *
97
+ * 2. Spiral(螺旋)
98
+ * - 在环形基础上添加高度递增(y++)
99
+ * - 数量大时视觉效果最强
100
+ *
101
+ * 3. Grid(网格)
102
+ * - 类似棋盘布局
103
+ * - spacing 控制网格大小
104
+ * - z 不变或小幅度变化
105
+ *
106
+ * 4. Radial(径向扩散)
107
+ * - 从中心向外 “爆炸式” 发散
108
+ * - 对于大型组件分解展示非常适合
109
+ *
110
+ *
111
+ * ----------------------------------------------------------------------
112
+ * 📌 使用示例(业务层 Vue)
113
+ * ----------------------------------------------------------------------
114
+ *
115
+ * const exploder = new GroupExploder(scene, camera, controls);
116
+ *
117
+ * onMounted(() => {
118
+ * exploder.setMeshes(new Set([meshA, meshB, meshC]));
119
+ * });
120
+ *
121
+ * const triggerExplode = () => {
122
+ * exploder.explode({
123
+ * mode: 'ring',
124
+ * spacing: 3,
125
+ * duration: 1200,
126
+ * lift: 0.8,
127
+ * cameraPadding: 1.3,
128
+ * dimOthers: { enabled: true, opacity: 0.2 },
129
+ * });
130
+ * };
131
+ *
132
+ * const triggerRestore = () => {
133
+ * exploder.restore(600);
134
+ * };
135
+ *
136
+ */
137
+
138
+ type ArrangeMode = 'ring' | 'spiral' | 'grid' | 'radial';
139
+ type ExplodeOptions = {
140
+ mode?: ArrangeMode;
141
+ spacing?: number;
142
+ duration?: number;
143
+ lift?: number;
144
+ cameraPadding?: number;
145
+ autoRestorePrev?: boolean;
146
+ dimOthers?: {
147
+ enabled: boolean;
148
+ opacity?: number;
149
+ };
150
+ debug?: boolean;
151
+ };
152
+ declare class GroupExploder {
153
+ private scene;
154
+ private camera;
155
+ private controls?;
156
+ private currentSet;
157
+ private stateMap;
158
+ private prevSet;
159
+ private prevStateMap;
160
+ private materialContexts;
161
+ private materialSnaps;
162
+ private contextMaterials;
163
+ private animId;
164
+ private cameraAnimId;
165
+ private isExploded;
166
+ private isInitialized;
167
+ onLog?: (s: string) => void;
168
+ constructor(scene: THREE.Scene, camera: THREE.PerspectiveCamera | THREE.Camera, controls?: {
169
+ target?: THREE.Vector3;
170
+ update?: () => void;
171
+ });
172
+ private log;
173
+ init(): void;
174
+ /**
175
+ * setMeshes(newSet):
176
+ * - Detects content-level changes even if same Set reference is used.
177
+ * - Preserves prevSet/stateMap to allow async restore when needed.
178
+ * - Ensures stateMap contains snapshots for *all meshes in the new set*.
179
+ */
180
+ setMeshes(newSet: Set<THREE.Mesh> | null, options?: {
181
+ autoRestorePrev?: boolean;
182
+ restoreDuration?: number;
183
+ }): Promise<void>;
184
+ /**
185
+ * ensureSnapshotsForSet: for each mesh in set, ensure stateMap has an entry.
186
+ * If missing, record current matrixWorld as originalMatrixWorld (best-effort).
187
+ */
188
+ private ensureSnapshotsForSet;
189
+ /**
190
+ * explode: compute targets first, compute targetBound using targets + mesh radii,
191
+ * animate camera to that targetBound, then animate meshes to targets.
192
+ */
193
+ explode(opts?: ExplodeOptions): Promise<void>;
194
+ restore(duration?: number): Promise<void>;
195
+ /**
196
+ * restoreSet: reparent and restore transforms using provided stateMap.
197
+ * If missing stateMap entry for a mesh, use fallbacks:
198
+ * 1) mesh.userData.__originalMatrixWorld (if present)
199
+ * 2) mesh.matrixWorld (current) -> smooth lerp to itself (no-op visually)
200
+ */
201
+ private restoreSet;
202
+ private applyDimToOthers;
203
+ private cleanContextsForMeshes;
204
+ private computeBoundingSphereForMeshes;
205
+ private computeBoundingSphereForPositionsAndMeshes;
206
+ private computeTargetsByMode;
207
+ private animateCameraToFit;
208
+ private getCameraLookAtPoint;
209
+ private cancelAnimations;
210
+ dispose(restoreBefore?: boolean): Promise<void>;
211
+ }
212
+
213
+ export { GroupExploder };
214
+ export type { ExplodeOptions };