@chocozhang/three-model-render 1.0.3 → 1.0.4

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 (42) hide show
  1. package/README.md +93 -96
  2. package/dist/camera/index.d.ts +59 -36
  3. package/dist/camera/index.js +77 -57
  4. package/dist/camera/index.js.map +1 -1
  5. package/dist/camera/index.mjs +77 -57
  6. package/dist/camera/index.mjs.map +1 -1
  7. package/dist/core/index.d.ts +60 -27
  8. package/dist/core/index.js +124 -95
  9. package/dist/core/index.js.map +1 -1
  10. package/dist/core/index.mjs +124 -95
  11. package/dist/core/index.mjs.map +1 -1
  12. package/dist/effect/index.d.ts +47 -134
  13. package/dist/effect/index.js +109 -65
  14. package/dist/effect/index.js.map +1 -1
  15. package/dist/effect/index.mjs +109 -65
  16. package/dist/effect/index.mjs.map +1 -1
  17. package/dist/index.d.ts +397 -341
  18. package/dist/index.js +651 -472
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs +651 -472
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/interaction/index.d.ts +85 -52
  23. package/dist/interaction/index.js +161 -133
  24. package/dist/interaction/index.js.map +1 -1
  25. package/dist/interaction/index.mjs +161 -133
  26. package/dist/interaction/index.mjs.map +1 -1
  27. package/dist/loader/index.d.ts +89 -56
  28. package/dist/loader/index.js +115 -76
  29. package/dist/loader/index.js.map +1 -1
  30. package/dist/loader/index.mjs +115 -76
  31. package/dist/loader/index.mjs.map +1 -1
  32. package/dist/setup/index.d.ts +28 -18
  33. package/dist/setup/index.js +33 -24
  34. package/dist/setup/index.js.map +1 -1
  35. package/dist/setup/index.mjs +33 -24
  36. package/dist/setup/index.mjs.map +1 -1
  37. package/dist/ui/index.d.ts +18 -7
  38. package/dist/ui/index.js +32 -22
  39. package/dist/ui/index.js.map +1 -1
  40. package/dist/ui/index.mjs +32 -22
  41. package/dist/ui/index.mjs.map +1 -1
  42. package/package.json +2 -2
@@ -2,6 +2,17 @@ import * as THREE from 'three';
2
2
  import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass';
3
3
  import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
4
4
 
5
+ /**
6
+ * @file labelManager.ts
7
+ * @description
8
+ * Manages HTML labels attached to 3D objects. Efficiently updates label positions based on camera movement.
9
+ *
10
+ * @best-practice
11
+ * - Use `addChildModelLabels` to label parts of a loaded model.
12
+ * - Labels are HTML elements overlaid on the canvas.
13
+ * - Supports performance optimization via caching and visibility culling.
14
+ */
15
+
5
16
  interface LabelOptions {
6
17
  fontSize?: string;
7
18
  color?: string;
@@ -18,23 +29,34 @@ interface LabelManager {
18
29
  isRunning: () => boolean;
19
30
  }
20
31
  /**
21
- * 给子模型添加头顶标签(支持 Mesh Group)- 优化版
32
+ * Add overhead labels to child models (supports Mesh and Group)
22
33
  *
23
- * ✨ 性能优化:
24
- * - 缓存包围盒,避免每帧重复计算
25
- * - 支持暂停/恢复更新
26
- * - 可配置更新间隔,降低 CPU 占用
27
- * - 只在可见时更新,隐藏时自动暂停
34
+ * Features:
35
+ * - Caches bounding boxes to avoid repetitive calculation every frame
36
+ * - Supports pause/resume
37
+ * - Configurable update interval to reduce CPU usage
38
+ * - Automatically pauses when hidden
28
39
  *
29
- * @param camera THREE.Camera - 场景摄像机
30
- * @param renderer THREE.WebGLRenderer - 渲染器,用于屏幕尺寸
31
- * @param parentModel THREE.Object3D - FBX 根节点或 Group
32
- * @param modelLabelsMap Record<string,string> - 模型 name 标签文字 映射表
33
- * @param options LabelOptions - 可选标签样式配置
34
- * @returns LabelManager - 包含 pause/resume/dispose 的管理接口
40
+ * @param camera THREE.Camera - Scene camera
41
+ * @param renderer THREE.WebGLRenderer - Renderer, used for screen size
42
+ * @param parentModel THREE.Object3D - FBX root node or Group
43
+ * @param modelLabelsMap Record<string,string> - Map of model name to label text
44
+ * @param options LabelOptions - Optional label style configuration
45
+ * @returns LabelManager - Management interface containing pause/resume/dispose
35
46
  */
36
47
  declare function addChildModelLabels(camera: THREE.Camera, renderer: THREE.WebGLRenderer, parentModel: THREE.Object3D, modelLabelsMap: Record<string, string>, options?: LabelOptions): LabelManager;
37
48
 
49
+ /**
50
+ * @file hoverEffect.ts
51
+ * @description
52
+ * Singleton highlight effect manager. Uses OutlinePass to create a breathing highlight effect on hovered objects.
53
+ *
54
+ * @best-practice
55
+ * - Initialize once in your setup/mounted hook.
56
+ * - Call `updateHighlightNames` to filter which objects are interactive.
57
+ * - Automatically handles mousemove throttling and cleanup on dispose.
58
+ */
59
+
38
60
  type HoverBreathOptions = {
39
61
  camera: THREE.Camera;
40
62
  scene: THREE.Scene;
@@ -47,13 +69,13 @@ type HoverBreathOptions = {
47
69
  throttleDelay?: number;
48
70
  };
49
71
  /**
50
- * 创建单例高亮器 —— 推荐在 mounted 时创建一次
51
- * 返回 { updateHighlightNames, dispose, getHoveredName } 接口
72
+ * Create a singleton highlighter - Recommended to create once on mount
73
+ * Returns { updateHighlightNames, dispose, getHoveredName } interface
52
74
  *
53
- * ✨ 性能优化:
54
- * - hover 对象时自动暂停动画
55
- * - mousemove 节流处理,避免过度计算
56
- * - 使用 passive 事件监听器提升滚动性能
75
+ * Features:
76
+ * - Automatically pauses animation when no object is hovered
77
+ * - Throttles mousemove events to avoid excessive calculation
78
+ * - Uses passive event listeners to improve scrolling performance
57
79
  */
58
80
  declare function enableHoverBreath(opts: HoverBreathOptions): {
59
81
  updateHighlightNames: (names: string[] | null) => void;
@@ -63,7 +85,18 @@ declare function enableHoverBreath(opts: HoverBreathOptions): {
63
85
  };
64
86
 
65
87
  /**
66
- * 后期处理配置选项
88
+ * @file postProcessing.ts
89
+ * @description
90
+ * Manages the post-processing chain, specifically for Outline effects and Gamma correction.
91
+ *
92
+ * @best-practice
93
+ * - call `initPostProcessing` after creating your renderer and scene.
94
+ * - Use the returned `composer` in your render loop instead of `renderer.render`.
95
+ * - Handles resizing automatically via the `resize` method.
96
+ */
97
+
98
+ /**
99
+ * Post-processing configuration options
67
100
  */
68
101
  interface PostProcessingOptions {
69
102
  edgeStrength?: number;
@@ -74,7 +107,7 @@ interface PostProcessingOptions {
74
107
  resolutionScale?: number;
75
108
  }
76
109
  /**
77
- * 后期处理管理接口
110
+ * Post-processing management interface
78
111
  */
79
112
  interface PostProcessingManager {
80
113
  composer: EffectComposer;
@@ -83,18 +116,18 @@ interface PostProcessingManager {
83
116
  dispose: () => void;
84
117
  }
85
118
  /**
86
- * 初始化描边相关信息(包含 OutlinePass)- 优化版
119
+ * Initialize outline-related information (contains OutlinePass)
87
120
  *
88
- * ✨ 功能增强:
89
- * - 支持窗口 resize 自动更新
90
- * - 可配置分辨率缩放提升性能
91
- * - 完善的资源释放管理
121
+ * Capabilities:
122
+ * - Supports automatic update on window resize
123
+ * - Configurable resolution scale for performance improvement
124
+ * - Comprehensive resource disposal management
92
125
  *
93
126
  * @param renderer THREE.WebGLRenderer
94
127
  * @param scene THREE.Scene
95
128
  * @param camera THREE.Camera
96
- * @param options PostProcessingOptions - 可选配置
97
- * @returns PostProcessingManager - 包含 composer/outlinePass/resize/dispose 的管理接口
129
+ * @param options PostProcessingOptions - Optional configuration
130
+ * @returns PostProcessingManager - Management interface containing composer/outlinePass/resize/dispose
98
131
  */
99
132
  declare function initPostProcessing(renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera, options?: PostProcessingOptions): PostProcessingManager;
100
133
 
@@ -27,25 +27,35 @@ function _interopNamespaceDefault(e) {
27
27
  var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
28
28
 
29
29
  /**
30
- * 给子模型添加头顶标签(支持 Mesh 和 Group)- 优化版
30
+ * @file labelManager.ts
31
+ * @description
32
+ * Manages HTML labels attached to 3D objects. Efficiently updates label positions based on camera movement.
31
33
  *
32
- * ✨ 性能优化:
33
- * - 缓存包围盒,避免每帧重复计算
34
- * - 支持暂停/恢复更新
35
- * - 可配置更新间隔,降低 CPU 占用
36
- * - 只在可见时更新,隐藏时自动暂停
34
+ * @best-practice
35
+ * - Use `addChildModelLabels` to label parts of a loaded model.
36
+ * - Labels are HTML elements overlaid on the canvas.
37
+ * - Supports performance optimization via caching and visibility culling.
38
+ */
39
+ /**
40
+ * Add overhead labels to child models (supports Mesh and Group)
41
+ *
42
+ * Features:
43
+ * - Caches bounding boxes to avoid repetitive calculation every frame
44
+ * - Supports pause/resume
45
+ * - Configurable update interval to reduce CPU usage
46
+ * - Automatically pauses when hidden
37
47
  *
38
- * @param camera THREE.Camera - 场景摄像机
39
- * @param renderer THREE.WebGLRenderer - 渲染器,用于屏幕尺寸
40
- * @param parentModel THREE.Object3D - FBX 根节点或 Group
41
- * @param modelLabelsMap Record<string,string> - 模型 name 标签文字 映射表
42
- * @param options LabelOptions - 可选标签样式配置
43
- * @returns LabelManager - 包含 pause/resume/dispose 的管理接口
48
+ * @param camera THREE.Camera - Scene camera
49
+ * @param renderer THREE.WebGLRenderer - Renderer, used for screen size
50
+ * @param parentModel THREE.Object3D - FBX root node or Group
51
+ * @param modelLabelsMap Record<string,string> - Map of model name to label text
52
+ * @param options LabelOptions - Optional label style configuration
53
+ * @returns LabelManager - Management interface containing pause/resume/dispose
44
54
  */
45
55
  function addChildModelLabels(camera, renderer, parentModel, modelLabelsMap, options) {
46
- // 防御性检查:确保 parentModel 已加载
56
+ // Defensive check: ensure parentModel is loaded
47
57
  if (!parentModel || typeof parentModel.traverse !== 'function') {
48
- console.error('parentModel 无效,请确保 FBX 模型已加载完成');
58
+ console.error('parentModel invalid, please ensure the FBX model is loaded');
49
59
  return {
50
60
  pause: () => { },
51
61
  resume: () => { },
@@ -53,48 +63,48 @@ function addChildModelLabels(camera, renderer, parentModel, modelLabelsMap, opti
53
63
  isRunning: () => false
54
64
  };
55
65
  }
56
- // 配置项
66
+ // Configuration
57
67
  const enableCache = (options === null || options === void 0 ? void 0 : options.enableCache) !== false;
58
68
  const updateInterval = (options === null || options === void 0 ? void 0 : options.updateInterval) || 0;
59
- // 创建标签容器,绝对定位,放在 body
69
+ // Create label container, absolute positioning, attached to body
60
70
  const container = document.createElement('div');
61
71
  container.style.position = 'absolute';
62
72
  container.style.top = '0';
63
73
  container.style.left = '0';
64
- container.style.pointerEvents = 'none'; // 避免阻挡鼠标事件
74
+ container.style.pointerEvents = 'none'; // Avoid blocking mouse events
65
75
  container.style.zIndex = '1000';
66
76
  document.body.appendChild(container);
67
77
  const labels = [];
68
- // 状态管理
78
+ // State management
69
79
  let rafId = null;
70
80
  let isPaused = false;
71
81
  let lastUpdateTime = 0;
72
- // 遍历所有子模型
82
+ // Traverse all child models
73
83
  parentModel.traverse((child) => {
74
84
  var _a;
75
- // 只处理 Mesh Group
85
+ // Only process Mesh or Group
76
86
  if ((child.isMesh || child.type === 'Group')) {
77
- // 动态匹配 name,防止 undefined
87
+ // Dynamic matching of name to prevent undefined
78
88
  const labelText = (_a = Object.entries(modelLabelsMap).find(([key]) => child.name.includes(key))) === null || _a === void 0 ? void 0 : _a[1];
79
89
  if (!labelText)
80
- return; // 没有匹配标签则跳过
81
- // 创建 DOM 标签
90
+ return; // Skip if no matching label
91
+ // Create DOM label
82
92
  const el = document.createElement('div');
83
93
  el.innerText = labelText;
84
- // 样式直接在 JS 中定义,可通过 options 覆盖
94
+ // Styles defined in JS, can be overridden via options
85
95
  el.style.position = 'absolute';
86
96
  el.style.color = (options === null || options === void 0 ? void 0 : options.color) || '#fff';
87
97
  el.style.background = (options === null || options === void 0 ? void 0 : options.background) || 'rgba(0,0,0,0.6)';
88
98
  el.style.padding = (options === null || options === void 0 ? void 0 : options.padding) || '4px 8px';
89
99
  el.style.borderRadius = (options === null || options === void 0 ? void 0 : options.borderRadius) || '4px';
90
100
  el.style.fontSize = (options === null || options === void 0 ? void 0 : options.fontSize) || '14px';
91
- el.style.transform = 'translate(-50%, -100%)'; // 让标签在模型正上方
101
+ el.style.transform = 'translate(-50%, -100%)'; // Position label directly above the model
92
102
  el.style.whiteSpace = 'nowrap';
93
103
  el.style.pointerEvents = 'none';
94
104
  el.style.transition = 'opacity 0.2s ease';
95
- // 加入容器
105
+ // Append to container
96
106
  container.appendChild(el);
97
- // 初始化缓存
107
+ // Initialize cache
98
108
  const cachedBox = new THREE__namespace.Box3().setFromObject(child);
99
109
  const center = new THREE__namespace.Vector3();
100
110
  cachedBox.getCenter(center);
@@ -109,7 +119,7 @@ function addChildModelLabels(camera, renderer, parentModel, modelLabelsMap, opti
109
119
  }
110
120
  });
111
121
  /**
112
- * 更新缓存的包围盒(仅在模型变换时调用)
122
+ * Update cached bounding box (called only when model transforms)
113
123
  */
114
124
  const updateCache = (labelData) => {
115
125
  labelData.cachedBox.setFromObject(labelData.object);
@@ -119,18 +129,18 @@ function addChildModelLabels(camera, renderer, parentModel, modelLabelsMap, opti
119
129
  labelData.needsUpdate = false;
120
130
  };
121
131
  /**
122
- * 获取对象顶部世界坐标(使用缓存)
132
+ * Get object top world coordinates (using cache)
123
133
  */
124
134
  const getObjectTopPosition = (labelData) => {
125
135
  if (enableCache) {
126
- // 检查对象是否发生变换
136
+ // Check if object has transformed
127
137
  if (labelData.needsUpdate || labelData.object.matrixWorldNeedsUpdate) {
128
138
  updateCache(labelData);
129
139
  }
130
140
  return labelData.cachedTopPos;
131
141
  }
132
142
  else {
133
- // 不使用缓存,每次都重新计算
143
+ // Do not use cache, recalculate every time
134
144
  const box = new THREE__namespace.Box3().setFromObject(labelData.object);
135
145
  const center = new THREE__namespace.Vector3();
136
146
  box.getCenter(center);
@@ -138,15 +148,15 @@ function addChildModelLabels(camera, renderer, parentModel, modelLabelsMap, opti
138
148
  }
139
149
  };
140
150
  /**
141
- * 更新标签位置函数
151
+ * Update label positions function
142
152
  */
143
153
  function updateLabels(timestamp = 0) {
144
- // 检查是否暂停
154
+ // Check pause state
145
155
  if (isPaused) {
146
156
  rafId = null;
147
157
  return;
148
158
  }
149
- // 检查更新间隔
159
+ // Check update interval
150
160
  if (updateInterval > 0 && timestamp - lastUpdateTime < updateInterval) {
151
161
  rafId = requestAnimationFrame(updateLabels);
152
162
  return;
@@ -156,22 +166,22 @@ function addChildModelLabels(camera, renderer, parentModel, modelLabelsMap, opti
156
166
  const height = renderer.domElement.clientHeight;
157
167
  labels.forEach((labelData) => {
158
168
  const { el } = labelData;
159
- const pos = getObjectTopPosition(labelData); // 使用缓存的顶部坐标
160
- pos.project(camera); // 转到屏幕坐标
161
- const x = (pos.x * 0.5 + 0.5) * width; // 屏幕 X
162
- const y = (-(pos.y * 0.5) + 0.5) * height; // 屏幕 Y
163
- // 控制标签显示/隐藏(摄像机后方隐藏)
169
+ const pos = getObjectTopPosition(labelData); // Use cached top position
170
+ pos.project(camera); // Convert to screen coordinates
171
+ const x = (pos.x * 0.5 + 0.5) * width; // Screen X
172
+ const y = (-(pos.y * 0.5) + 0.5) * height; // Screen Y
173
+ // Control label visibility (hidden when behind camera)
164
174
  const isVisible = pos.z < 1;
165
175
  el.style.opacity = isVisible ? '1' : '0';
166
176
  el.style.display = isVisible ? 'block' : 'none';
167
- el.style.transform = `translate(-50%, -100%) translate(${x}px, ${y}px)`; // 屏幕位置
177
+ el.style.transform = `translate(-50%, -100%) translate(${x}px, ${y}px)`; // Screen position
168
178
  });
169
- rafId = requestAnimationFrame(updateLabels); // 循环更新
179
+ rafId = requestAnimationFrame(updateLabels); // Loop update
170
180
  }
171
- // 启动更新
181
+ // Start update
172
182
  updateLabels();
173
183
  /**
174
- * 暂停更新
184
+ * Pause updates
175
185
  */
176
186
  const pause = () => {
177
187
  isPaused = true;
@@ -181,7 +191,7 @@ function addChildModelLabels(camera, renderer, parentModel, modelLabelsMap, opti
181
191
  }
182
192
  };
183
193
  /**
184
- * 恢复更新
194
+ * Resume updates
185
195
  */
186
196
  const resume = () => {
187
197
  if (!isPaused)
@@ -190,11 +200,11 @@ function addChildModelLabels(camera, renderer, parentModel, modelLabelsMap, opti
190
200
  updateLabels();
191
201
  };
192
202
  /**
193
- * 检查是否正在运行
203
+ * Check if running
194
204
  */
195
205
  const isRunning = () => !isPaused;
196
206
  /**
197
- * 清理函数:卸载所有 DOM 标签,取消动画,避免内存泄漏
207
+ * Cleanup function: Remove all DOM labels, cancel animation, avoid memory leaks
198
208
  */
199
209
  const dispose = () => {
200
210
  pause();
@@ -216,36 +226,45 @@ function addChildModelLabels(camera, renderer, parentModel, modelLabelsMap, opti
216
226
  };
217
227
  }
218
228
 
219
- // src/utils/hoverBreathEffectByNameSingleton.ts
220
229
  /**
221
- * 创建单例高亮器 —— 推荐在 mounted 时创建一次
222
- * 返回 { updateHighlightNames, dispose, getHoveredName } 接口
230
+ * @file hoverEffect.ts
231
+ * @description
232
+ * Singleton highlight effect manager. Uses OutlinePass to create a breathing highlight effect on hovered objects.
223
233
  *
224
- * ✨ 性能优化:
225
- * - hover 对象时自动暂停动画
226
- * - mousemove 节流处理,避免过度计算
227
- * - 使用 passive 事件监听器提升滚动性能
234
+ * @best-practice
235
+ * - Initialize once in your setup/mounted hook.
236
+ * - Call `updateHighlightNames` to filter which objects are interactive.
237
+ * - Automatically handles mousemove throttling and cleanup on dispose.
238
+ */
239
+ /**
240
+ * Create a singleton highlighter - Recommended to create once on mount
241
+ * Returns { updateHighlightNames, dispose, getHoveredName } interface
242
+ *
243
+ * Features:
244
+ * - Automatically pauses animation when no object is hovered
245
+ * - Throttles mousemove events to avoid excessive calculation
246
+ * - Uses passive event listeners to improve scrolling performance
228
247
  */
229
248
  function enableHoverBreath(opts) {
230
- const { camera, scene, renderer, outlinePass, highlightNames = null, minStrength = 2, maxStrength = 5, speed = 4, throttleDelay = 16, // 默认约 60fps
249
+ const { camera, scene, renderer, outlinePass, highlightNames = null, minStrength = 2, maxStrength = 5, speed = 4, throttleDelay = 16, // Default ~60fps
231
250
  } = opts;
232
251
  const raycaster = new THREE__namespace.Raycaster();
233
252
  const mouse = new THREE__namespace.Vector2();
234
253
  let hovered = null;
235
254
  let time = 0;
236
255
  let animationId = null;
237
- // highlightSet: null 表示 all; empty Set 表示 none
256
+ // highlightSet: null means all; empty Set means none
238
257
  let highlightSet = highlightNames === null ? null : new Set(highlightNames);
239
- // 节流相关
258
+ // Throttling related
240
259
  let lastMoveTime = 0;
241
260
  let rafPending = false;
242
261
  function setHighlightNames(names) {
243
262
  highlightSet = names === null ? null : new Set(names);
244
- // 如果当前 hovered 不在新名单中,及时清理 selection
263
+ // If current hovered object is not in the new list, clean up selection immediately
245
264
  if (hovered && highlightSet && !highlightSet.has(hovered.name)) {
246
265
  hovered = null;
247
266
  outlinePass.selectedObjects = [];
248
- // 暂停动画
267
+ // Pause animation
249
268
  if (animationId !== null) {
250
269
  cancelAnimationFrame(animationId);
251
270
  animationId = null;
@@ -253,13 +272,13 @@ function enableHoverBreath(opts) {
253
272
  }
254
273
  }
255
274
  /**
256
- * 节流版本的 mousemove 处理
275
+ * Throttled mousemove handler
257
276
  */
258
277
  function onMouseMove(ev) {
259
278
  const now = performance.now();
260
- // 节流:如果距上次处理时间小于阈值,跳过
279
+ // Throttle: if time since last process is less than threshold, skip
261
280
  if (now - lastMoveTime < throttleDelay) {
262
- // 使用 RAF 延迟处理,避免丢失最后一次事件
281
+ // Use RAF to process the latest event later, ensuring the last event isn't lost
263
282
  if (!rafPending) {
264
283
  rafPending = true;
265
284
  requestAnimationFrame(() => {
@@ -273,24 +292,24 @@ function enableHoverBreath(opts) {
273
292
  processMouseMove(ev);
274
293
  }
275
294
  /**
276
- * 实际的 mousemove 逻辑
295
+ * Actual mousemove logic
277
296
  */
278
297
  function processMouseMove(ev) {
279
298
  const rect = renderer.domElement.getBoundingClientRect();
280
299
  mouse.x = ((ev.clientX - rect.left) / rect.width) * 2 - 1;
281
300
  mouse.y = -((ev.clientY - rect.top) / rect.height) * 2 + 1;
282
301
  raycaster.setFromCamera(mouse, camera);
283
- // 深度检测 scene 的所有子对象(true
302
+ // Deep detect all children of the scene (true)
284
303
  const intersects = raycaster.intersectObjects(scene.children, true);
285
304
  if (intersects.length > 0) {
286
305
  const obj = intersects[0].object;
287
- // 判断是否允许被高亮
306
+ // Determine if it is allowed to be highlighted
288
307
  const allowed = highlightSet === null ? true : highlightSet.has(obj.name);
289
308
  if (allowed) {
290
309
  if (hovered !== obj) {
291
310
  hovered = obj;
292
311
  outlinePass.selectedObjects = [obj];
293
- // 启动动画(如果未运行)
312
+ // Start animation (if not running)
294
313
  if (animationId === null) {
295
314
  animate();
296
315
  }
@@ -300,7 +319,7 @@ function enableHoverBreath(opts) {
300
319
  if (hovered !== null) {
301
320
  hovered = null;
302
321
  outlinePass.selectedObjects = [];
303
- // 停止动画
322
+ // Stop animation
304
323
  if (animationId !== null) {
305
324
  cancelAnimationFrame(animationId);
306
325
  animationId = null;
@@ -312,7 +331,7 @@ function enableHoverBreath(opts) {
312
331
  if (hovered !== null) {
313
332
  hovered = null;
314
333
  outlinePass.selectedObjects = [];
315
- // 停止动画
334
+ // Stop animation
316
335
  if (animationId !== null) {
317
336
  cancelAnimationFrame(animationId);
318
337
  animationId = null;
@@ -321,10 +340,10 @@ function enableHoverBreath(opts) {
321
340
  }
322
341
  }
323
342
  /**
324
- * 动画循环 - 只在有 hovered 对象时运行
343
+ * Animation loop - only runs when there is a hovered object
325
344
  */
326
345
  function animate() {
327
- // 如果没有 hovered 对象,停止动画
346
+ // If no hovered object, stop animation
328
347
  if (!hovered) {
329
348
  animationId = null;
330
349
  return;
@@ -334,11 +353,11 @@ function enableHoverBreath(opts) {
334
353
  const strength = minStrength + ((Math.sin(time) + 1) / 2) * (maxStrength - minStrength);
335
354
  outlinePass.edgeStrength = strength;
336
355
  }
337
- // 启动(只调用一次)
338
- // 使用 passive 提升滚动性能
356
+ // Start (called only once)
357
+ // Use passive to improve scrolling performance
339
358
  renderer.domElement.addEventListener('mousemove', onMouseMove, { passive: true });
340
- // 注意:不在这里启动 animate,等有 hover 对象时再启动
341
- // refresh: 如果你在某些情况下需要强制清理 selectedObjects
359
+ // Note: Do not start animate here, wait until there is a hover object
360
+ // refresh: Forcibly clean up selectedObjects if needed
342
361
  function refreshSelection() {
343
362
  if (hovered && highlightSet && !highlightSet.has(hovered.name)) {
344
363
  hovered = null;
@@ -359,7 +378,7 @@ function enableHoverBreath(opts) {
359
378
  animationId = null;
360
379
  }
361
380
  outlinePass.selectedObjects = [];
362
- // 清空引用
381
+ // Clear references
363
382
  hovered = null;
364
383
  highlightSet = null;
365
384
  }
@@ -372,23 +391,33 @@ function enableHoverBreath(opts) {
372
391
  }
373
392
 
374
393
  /**
375
- * 初始化描边相关信息(包含 OutlinePass)- 优化版
394
+ * @file postProcessing.ts
395
+ * @description
396
+ * Manages the post-processing chain, specifically for Outline effects and Gamma correction.
397
+ *
398
+ * @best-practice
399
+ * - call `initPostProcessing` after creating your renderer and scene.
400
+ * - Use the returned `composer` in your render loop instead of `renderer.render`.
401
+ * - Handles resizing automatically via the `resize` method.
402
+ */
403
+ /**
404
+ * Initialize outline-related information (contains OutlinePass)
376
405
  *
377
- * ✨ 功能增强:
378
- * - 支持窗口 resize 自动更新
379
- * - 可配置分辨率缩放提升性能
380
- * - 完善的资源释放管理
406
+ * Capabilities:
407
+ * - Supports automatic update on window resize
408
+ * - Configurable resolution scale for performance improvement
409
+ * - Comprehensive resource disposal management
381
410
  *
382
411
  * @param renderer THREE.WebGLRenderer
383
412
  * @param scene THREE.Scene
384
413
  * @param camera THREE.Camera
385
- * @param options PostProcessingOptions - 可选配置
386
- * @returns PostProcessingManager - 包含 composer/outlinePass/resize/dispose 的管理接口
414
+ * @param options PostProcessingOptions - Optional configuration
415
+ * @returns PostProcessingManager - Management interface containing composer/outlinePass/resize/dispose
387
416
  */
388
417
  function initPostProcessing(renderer, scene, camera, options = {}) {
389
- // 默认配置
418
+ // Default configuration
390
419
  const { edgeStrength = 4, edgeGlow = 1, edgeThickness = 2, visibleEdgeColor = '#ffee00', hiddenEdgeColor = '#000000', resolutionScale = 1.0 } = options;
391
- // 获取渲染器实际尺寸
420
+ // Get renderer actual size
392
421
  const getSize = () => {
393
422
  const width = renderer.domElement.clientWidth;
394
423
  const height = renderer.domElement.clientHeight;
@@ -398,12 +427,12 @@ function initPostProcessing(renderer, scene, camera, options = {}) {
398
427
  };
399
428
  };
400
429
  const size = getSize();
401
- // 创建 EffectComposer
430
+ // Create EffectComposer
402
431
  const composer = new EffectComposer.EffectComposer(renderer);
403
- // 基础 RenderPass
432
+ // Basic RenderPass
404
433
  const renderPass = new RenderPass.RenderPass(scene, camera);
405
434
  composer.addPass(renderPass);
406
- // OutlinePass 用于模型描边
435
+ // OutlinePass for model outlining
407
436
  const outlinePass = new OutlinePass.OutlinePass(new THREE__namespace.Vector2(size.width, size.height), scene, camera);
408
437
  outlinePass.edgeStrength = edgeStrength;
409
438
  outlinePass.edgeGlow = edgeGlow;
@@ -411,34 +440,34 @@ function initPostProcessing(renderer, scene, camera, options = {}) {
411
440
  outlinePass.visibleEdgeColor.set(visibleEdgeColor);
412
441
  outlinePass.hiddenEdgeColor.set(hiddenEdgeColor);
413
442
  composer.addPass(outlinePass);
414
- // Gamma 校正
443
+ // Gamma correction
415
444
  const gammaPass = new ShaderPass.ShaderPass(GammaCorrectionShader.GammaCorrectionShader);
416
445
  composer.addPass(gammaPass);
417
446
  /**
418
- * resize 处理函数
419
- * @param width 可选宽度,不传则使用 renderer.domElement 的实际宽度
420
- * @param height 可选高度,不传则使用 renderer.domElement 的实际高度
447
+ * Handle resize
448
+ * @param width Optional width, uses renderer.domElement actual width if not provided
449
+ * @param height Optional height, uses renderer.domElement actual height if not provided
421
450
  */
422
451
  const resize = (width, height) => {
423
452
  const actualSize = width !== undefined && height !== undefined
424
453
  ? { width: Math.floor(width * resolutionScale), height: Math.floor(height * resolutionScale) }
425
454
  : getSize();
426
- // 更新 composer 尺寸
455
+ // Update composer size
427
456
  composer.setSize(actualSize.width, actualSize.height);
428
- // 更新 outlinePass 分辨率
457
+ // Update outlinePass resolution
429
458
  outlinePass.resolution.set(actualSize.width, actualSize.height);
430
459
  };
431
460
  /**
432
- * 释放资源
461
+ * Dispose resources
433
462
  */
434
463
  const dispose = () => {
435
- // 释放所有 passes
464
+ // Dipose all passes
436
465
  composer.passes.forEach(pass => {
437
466
  if (pass.dispose) {
438
467
  pass.dispose();
439
468
  }
440
469
  });
441
- // 清空 passes 数组
470
+ // Clear passes array
442
471
  composer.passes.length = 0;
443
472
  };
444
473
  return {