@chocozhang/three-model-render 1.0.2 → 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 +149 -530
  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 +1 -1
@@ -24,28 +24,38 @@ var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
24
24
  var BufferGeometryUtils__namespace = /*#__PURE__*/_interopNamespaceDefault(BufferGeometryUtils);
25
25
 
26
26
  /**
27
- * 创建模型点击高亮工具(OutlinePass 版)- 优化版
27
+ * @file clickHandler.ts
28
+ * @description
29
+ * Tool for handling model clicks and highlighting (OutlinePass version).
28
30
  *
29
- * ✨ 功能增强:
30
- * - 使用 AbortController 统一管理事件生命周期
31
- * - 支持防抖处理避免频繁触发
32
- * - 可自定义 Raycaster 参数
33
- * - 根据相机距离动态调整描边厚度
31
+ * @best-practice
32
+ * - Use `createModelClickHandler` to setup interaction.
33
+ * - Handles debouncing and click threshold automatically.
34
+ * - Cleanup using the returned dispose function.
35
+ */
36
+ /**
37
+ * Create Model Click Highlight Tool (OutlinePass Version) - Optimized
38
+ *
39
+ * Features:
40
+ * - Uses AbortController to unify event lifecycle management
41
+ * - Supports debounce to avoid frequent triggering
42
+ * - Customizable Raycaster parameters
43
+ * - Dynamically adjusts outline thickness based on camera distance
34
44
  *
35
- * @param camera 相机
36
- * @param scene 场景
37
- * @param renderer 渲染器
38
- * @param outlinePass 已初始化的 OutlinePass
39
- * @param onClick 点击回调
40
- * @param options 可选配置
41
- * @returns dispose 函数,用于清理事件和资源
45
+ * @param camera Camera
46
+ * @param scene Scene
47
+ * @param renderer Renderer
48
+ * @param outlinePass Initialized OutlinePass
49
+ * @param onClick Click callback
50
+ * @param options Optional configuration
51
+ * @returns Dispose function, used to clean up events and resources
42
52
  */
43
53
  function createModelClickHandler(camera, scene, renderer, outlinePass, onClick, options = {}) {
44
- // 配置项
54
+ // Configuration
45
55
  const { clickThreshold = 3, debounceDelay = 0, raycasterParams = {}, enableDynamicThickness = true, minThickness = 1, maxThickness = 10 } = options;
46
56
  const raycaster = new THREE__namespace.Raycaster();
47
57
  const mouse = new THREE__namespace.Vector2();
48
- // 应用 raycaster 自定义参数
58
+ // Apply Raycaster custom parameters
49
59
  if (raycasterParams.near !== undefined)
50
60
  raycaster.near = raycasterParams.near;
51
61
  if (raycasterParams.far !== undefined)
@@ -62,25 +72,25 @@ function createModelClickHandler(camera, scene, renderer, outlinePass, onClick,
62
72
  let startY = 0;
63
73
  let selectedObject = null;
64
74
  let debounceTimer = null;
65
- // 使用 AbortController 统一管理事件
75
+ // Use AbortController to manage events uniformly
66
76
  const abortController = new AbortController();
67
77
  const signal = abortController.signal;
68
- /** 恢复对象高亮(清空 OutlinePass.selectedObjects */
78
+ /** Restore object highlight (Clear OutlinePass.selectedObjects) */
69
79
  function restoreObject() {
70
80
  outlinePass.selectedObjects = [];
71
81
  }
72
- /** 鼠标按下记录位置 */
82
+ /** Record mouse down position */
73
83
  function handleMouseDown(event) {
74
84
  startX = event.clientX;
75
85
  startY = event.clientY;
76
86
  }
77
- /** 鼠标抬起判定点击或拖动(带防抖) */
87
+ /** Mouse up determines click or drag (with debounce) */
78
88
  function handleMouseUp(event) {
79
89
  const dx = Math.abs(event.clientX - startX);
80
90
  const dy = Math.abs(event.clientY - startY);
81
91
  if (dx > clickThreshold || dy > clickThreshold)
82
- return; // 拖动不触发点击
83
- // 防抖处理
92
+ return; // Drag does not trigger click
93
+ // Debounce processing
84
94
  if (debounceDelay > 0) {
85
95
  if (debounceTimer !== null) {
86
96
  clearTimeout(debounceTimer);
@@ -94,7 +104,7 @@ function createModelClickHandler(camera, scene, renderer, outlinePass, onClick,
94
104
  processClick(event);
95
105
  }
96
106
  }
97
- /** 实际的点击处理逻辑 */
107
+ /** Actual click processing logic */
98
108
  function processClick(event) {
99
109
  const rect = renderer.domElement.getBoundingClientRect();
100
110
  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
@@ -103,55 +113,64 @@ function createModelClickHandler(camera, scene, renderer, outlinePass, onClick,
103
113
  const intersects = raycaster.intersectObjects(scene.children, true);
104
114
  if (intersects.length > 0) {
105
115
  let object = intersects[0].object;
106
- // 点击不同模型,先清除之前高亮
116
+ // Click different model, clear previous highlight first
107
117
  if (selectedObject && selectedObject !== object)
108
118
  restoreObject();
109
119
  selectedObject = object;
110
- // highlightObject(selectedObject); // 可选:是否自动高亮
120
+ // highlightObject(selectedObject); // Optional: whether to auto highlight
111
121
  onClick(selectedObject, {
112
- name: selectedObject.name || '未命名模型',
122
+ name: selectedObject.name || 'Unnamed Model',
113
123
  position: selectedObject.getWorldPosition(new THREE__namespace.Vector3()),
114
124
  uuid: selectedObject.uuid
115
125
  });
116
126
  }
117
127
  else {
118
- // 点击空白 清除高亮
128
+ // Click blank -> Clear highlight
119
129
  if (selectedObject)
120
130
  restoreObject();
121
131
  selectedObject = null;
122
132
  onClick(null);
123
133
  }
124
134
  }
125
- // 使用 AbortController signal 注册事件
135
+ // Register events using signal from AbortController
126
136
  renderer.domElement.addEventListener('mousedown', handleMouseDown, { signal });
127
137
  renderer.domElement.addEventListener('mouseup', handleMouseUp, { signal });
128
- /** 销毁函数:解绑事件并清除高亮 */
138
+ /** Dispose function: Unbind events and clear highlight */
129
139
  return () => {
130
- // 清理防抖定时器
140
+ // Clear debounce timer
131
141
  if (debounceTimer !== null) {
132
142
  clearTimeout(debounceTimer);
133
143
  debounceTimer = null;
134
144
  }
135
- // 一次性解绑所有事件
145
+ // Unbind all events at once
136
146
  abortController.abort();
137
- // 清除高亮
147
+ // Clear highlight
138
148
  restoreObject();
139
- // 清空引用
149
+ // Clear reference
140
150
  selectedObject = null;
141
151
  };
142
152
  }
143
153
 
144
- // src/utils/ArrowGuide.ts
145
154
  /**
146
- * ArrowGuide - 优化版
147
- * 箭头引导效果工具,支持高亮模型并淡化其他对象
155
+ * @file arrowGuide.ts
156
+ * @description
157
+ * Arrow guide effect tool, supports highlighting models and fading other objects.
158
+ *
159
+ * @best-practice
160
+ * - Use `highlight` to focus on specific models.
161
+ * - Automatically manages materials and memory using WeakMap.
162
+ * - Call `dispose` when component unmounts.
163
+ */
164
+ /**
165
+ * ArrowGuide - Optimized Version
166
+ * Arrow guide effect tool, supports highlighting models and fading other objects.
148
167
  *
149
- * ✨ 优化内容:
150
- * - 使用 WeakMap 自动回收材质,避免内存泄漏
151
- * - 使用 AbortController 管理事件生命周期
152
- * - 添加材质复用机制,减少重复创建
153
- * - 改进 dispose 逻辑,确保完全释放资源
154
- * - 添加错误处理和边界检查
168
+ * Features:
169
+ * - Uses WeakMap for automatic material recycling, preventing memory leaks
170
+ * - Uses AbortController to manage event lifecycle
171
+ * - Adds material reuse mechanism to reuse materials
172
+ * - Improved dispose logic ensuring complete resource release
173
+ * - Adds error handling and boundary checks
155
174
  */
156
175
  class ArrowGuide {
157
176
  constructor(renderer, camera, scene, options) {
@@ -166,12 +185,12 @@ class ArrowGuide {
166
185
  this.clickThreshold = 10;
167
186
  this.raycaster = new THREE__namespace.Raycaster();
168
187
  this.mouse = new THREE__namespace.Vector2();
169
- // 使用 WeakMap 自动回收材质(GC 友好)
188
+ // Use WeakMap for automatic material recycling (GC friendly)
170
189
  this.originalMaterials = new WeakMap();
171
190
  this.fadedMaterials = new WeakMap();
172
- // AbortController 用于事件管理
191
+ // AbortController for event management
173
192
  this.abortController = null;
174
- // 配置:非高亮透明度和亮度
193
+ // Config: Non-highlight opacity and brightness
175
194
  this.fadeOpacity = 0.5;
176
195
  this.fadeBrightness = 0.1;
177
196
  this.clickThreshold = (_a = options === null || options === void 0 ? void 0 : options.clickThreshold) !== null && _a !== void 0 ? _a : 10;
@@ -181,30 +200,30 @@ class ArrowGuide {
181
200
  this.abortController = new AbortController();
182
201
  this.initEvents();
183
202
  }
184
- // —— 工具:缓存原材质(仅首次)
203
+ // Tool: Cache original material (first time only)
185
204
  cacheOriginalMaterial(mesh) {
186
205
  if (!this.originalMaterials.has(mesh)) {
187
206
  this.originalMaterials.set(mesh, mesh.material);
188
207
  }
189
208
  }
190
- // —— 工具:为某个材质克隆一个"半透明版本",保留所有贴图与参数
209
+ // Tool: Clone a "translucent version" for a material, preserving all maps and parameters
191
210
  makeFadedClone(mat) {
192
211
  const clone = mat.clone();
193
212
  const c = clone;
194
- // 只改透明相关参数,不改 map / normalMap / roughnessMap 等细节
213
+ // Only modify transparency parameters, do not modify detail maps like map / normalMap / roughnessMap
195
214
  c.transparent = true;
196
215
  if (typeof c.opacity === 'number')
197
216
  c.opacity = this.fadeOpacity;
198
217
  if (c.color && c.color.isColor) {
199
- c.color.multiplyScalar(this.fadeBrightness); // 颜色整体变暗
218
+ c.color.multiplyScalar(this.fadeBrightness); // Darken color overall
200
219
  }
201
- // 为了让箭头在透明建筑后也能顺畅显示,常用策略:不写深度,仅测试深度
220
+ // Common strategy for fluid display behind transparent objects: do not write depth, only test depth
202
221
  clone.depthWrite = false;
203
222
  clone.depthTest = true;
204
223
  clone.needsUpdate = true;
205
224
  return clone;
206
225
  }
207
- // —— 工具:为 mesh.material(可能是数组)批量克隆"半透明版本"
226
+ // Tool: Batch clone "translucent version" for mesh.material (could be array)
208
227
  createFadedMaterialFrom(mesh) {
209
228
  const orig = mesh.material;
210
229
  if (Array.isArray(orig)) {
@@ -213,7 +232,7 @@ class ArrowGuide {
213
232
  return this.makeFadedClone(orig);
214
233
  }
215
234
  /**
216
- * 设置箭头 Mesh
235
+ * Set Arrow Mesh
217
236
  */
218
237
  setArrowMesh(mesh) {
219
238
  this.lxMesh = mesh;
@@ -229,15 +248,15 @@ class ArrowGuide {
229
248
  mesh.visible = false;
230
249
  }
231
250
  catch (error) {
232
- console.error('ArrowGuide: 设置箭头材质失败', error);
251
+ console.error('ArrowGuide: Failed to set arrow material', error);
233
252
  }
234
253
  }
235
254
  /**
236
- * 高亮指定模型
255
+ * Highlight specified models
237
256
  */
238
257
  highlight(models) {
239
258
  if (!models || models.length === 0) {
240
- console.warn('ArrowGuide: 高亮模型列表为空');
259
+ console.warn('ArrowGuide: Highlight model list is empty');
241
260
  return;
242
261
  }
243
262
  this.modelBrightArr = models;
@@ -246,9 +265,9 @@ class ArrowGuide {
246
265
  this.lxMesh.visible = true;
247
266
  this.applyHighlight();
248
267
  }
249
- // 应用高亮效果:非高亮模型保留细节 使用"克隆后的半透明材质"
268
+ // Apply highlight effect: Non-highlighted models preserve details -> use "cloned translucent material"
250
269
  applyHighlight() {
251
- // 使用 Set 提升查找性能
270
+ // Use Set to improve lookup performance
252
271
  const keepMeshes = new Set();
253
272
  this.modelBrightArr.forEach(obj => {
254
273
  obj.traverse(child => {
@@ -260,21 +279,21 @@ class ArrowGuide {
260
279
  this.scene.traverse(obj => {
261
280
  if (obj.isMesh) {
262
281
  const mesh = obj;
263
- // 缓存原材质(用于恢复)
282
+ // Cache original material (for restoration)
264
283
  this.cacheOriginalMaterial(mesh);
265
284
  if (!keepMeshes.has(mesh)) {
266
- // 非高亮:如果还没给它生成过"半透明克隆材质",就创建一次
285
+ // Non-highlighted: if no "translucent clone material" generated yet, create one
267
286
  if (!this.fadedMaterials.has(mesh)) {
268
287
  const faded = this.createFadedMaterialFrom(mesh);
269
288
  this.fadedMaterials.set(mesh, faded);
270
289
  }
271
- // 替换为克隆材质(保留所有贴图/法线等细节)
290
+ // Replace with clone material (preserve all maps/normals details)
272
291
  const fadedMat = this.fadedMaterials.get(mesh);
273
292
  if (fadedMat)
274
293
  mesh.material = fadedMat;
275
294
  }
276
295
  else {
277
- // 高亮对象:确保回到原材质(避免上一次高亮后遗留)
296
+ // Highlighted object: ensure return to original material (avoid leftover from previous highlight)
278
297
  const orig = this.originalMaterials.get(mesh);
279
298
  if (orig && mesh.material !== orig) {
280
299
  mesh.material = orig;
@@ -285,16 +304,16 @@ class ArrowGuide {
285
304
  });
286
305
  }
287
306
  catch (error) {
288
- console.error('ArrowGuide: 应用高亮失败', error);
307
+ console.error('ArrowGuide: Failed to apply highlight', error);
289
308
  }
290
309
  }
291
- // 恢复为原材质 & 释放克隆材质
310
+ // Restore to original material & dispose clone material
292
311
  restore() {
293
312
  this.flowActive = false;
294
313
  if (this.lxMesh)
295
314
  this.lxMesh.visible = false;
296
315
  try {
297
- // 收集所有需要释放的材质
316
+ // Collect all materials to dispose
298
317
  const materialsToDispose = [];
299
318
  this.scene.traverse(obj => {
300
319
  if (obj.isMesh) {
@@ -304,7 +323,7 @@ class ArrowGuide {
304
323
  mesh.material = orig;
305
324
  mesh.material.needsUpdate = true;
306
325
  }
307
- // 收集待释放的淡化材质
326
+ // Collect faded materials to dispose
308
327
  const faded = this.fadedMaterials.get(mesh);
309
328
  if (faded) {
310
329
  if (Array.isArray(faded)) {
@@ -316,24 +335,24 @@ class ArrowGuide {
316
335
  }
317
336
  }
318
337
  });
319
- // 批量释放材质(不触碰贴图资源)
338
+ // Batch dispose materials (do not touch texture resources)
320
339
  materialsToDispose.forEach(mat => {
321
340
  try {
322
341
  mat.dispose();
323
342
  }
324
343
  catch (error) {
325
- console.error('ArrowGuide: 释放材质失败', error);
344
+ console.error('ArrowGuide: Failed to dispose material', error);
326
345
  }
327
346
  });
328
- // 创建新的 WeakMap(相当于清空)
347
+ // Create new WeakMap (equivalent to clearing)
329
348
  this.fadedMaterials = new WeakMap();
330
349
  }
331
350
  catch (error) {
332
- console.error('ArrowGuide: 恢复材质失败', error);
351
+ console.error('ArrowGuide: Failed to restore material', error);
333
352
  }
334
353
  }
335
354
  /**
336
- * 动画更新(每帧调用)
355
+ * Animation update (called every frame)
337
356
  */
338
357
  animate() {
339
358
  if (!this.flowActive || !this.lxMesh)
@@ -347,16 +366,16 @@ class ArrowGuide {
347
366
  }
348
367
  }
349
368
  catch (error) {
350
- console.error('ArrowGuide: 动画更新失败', error);
369
+ console.error('ArrowGuide: Animation update failed', error);
351
370
  }
352
371
  }
353
372
  /**
354
- * 初始化事件监听器
373
+ * Initialize event listeners
355
374
  */
356
375
  initEvents() {
357
376
  const dom = this.renderer.domElement;
358
377
  const signal = this.abortController.signal;
359
- // 使用 AbortController signal 自动管理事件生命周期
378
+ // Use AbortController signal to automatically manage event lifecycle
360
379
  dom.addEventListener('pointerdown', (e) => {
361
380
  this.pointerDownPos.set(e.clientX, e.clientY);
362
381
  }, { signal });
@@ -364,7 +383,7 @@ class ArrowGuide {
364
383
  const dx = Math.abs(e.clientX - this.pointerDownPos.x);
365
384
  const dy = Math.abs(e.clientY - this.pointerDownPos.y);
366
385
  if (dx > this.clickThreshold || dy > this.clickThreshold)
367
- return; // 拖拽
386
+ return; // Dragging
368
387
  const rect = dom.getBoundingClientRect();
369
388
  this.mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
370
389
  this.mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
@@ -378,21 +397,21 @@ class ArrowGuide {
378
397
  return true;
379
398
  });
380
399
  if (filtered.length === 0)
381
- this.restore(); // 点击空白恢复
400
+ this.restore(); // Click blank space to restore
382
401
  }, { signal });
383
402
  }
384
403
  /**
385
- * 释放所有资源
404
+ * Dispose all resources
386
405
  */
387
406
  dispose() {
388
- // 先恢复材质
407
+ // Restore materials first
389
408
  this.restore();
390
- // 使用 AbortController 一次性解绑所有事件
409
+ // Unbind all events at once using AbortController
391
410
  if (this.abortController) {
392
411
  this.abortController.abort();
393
412
  this.abortController = null;
394
413
  }
395
- // 清空引用
414
+ // Clear references
396
415
  this.modelBrightArr = [];
397
416
  this.lxMesh = null;
398
417
  this.fadedMaterials = new WeakMap();
@@ -401,50 +420,59 @@ class ArrowGuide {
401
420
  }
402
421
  }
403
422
 
404
- // utils/LiquidFillerGroup.ts
405
423
  /**
406
- * LiquidFillerGroup - 优化版
407
- * 支持单模型或多模型液位动画、独立颜色控制
424
+ * @file liquidFiller.ts
425
+ * @description
426
+ * Liquid filling effect for single or multiple models using local clipping planes.
408
427
  *
409
- * ✨ 优化内容:
410
- * - 使用 renderer.domElement 替代 window 事件
411
- * - 使用 AbortController 管理事件生命周期
412
- * - 添加错误处理和边界检查
413
- * - 优化动画管理,避免内存泄漏
414
- * - 完善资源释放逻辑
428
+ * @best-practice
429
+ * - Use `fillTo` to animate liquid level.
430
+ * - Supports multiple independent liquid levels.
431
+ * - Call `dispose` to clean up resources and event listeners.
432
+ */
433
+ /**
434
+ * LiquidFillerGroup - Optimized
435
+ * Supports single or multi-model liquid level animation with independent color control.
436
+ *
437
+ * Features:
438
+ * - Uses renderer.domElement instead of window events
439
+ * - Uses AbortController to manage event lifecycle
440
+ * - Adds error handling and boundary checks
441
+ * - Optimized animation management to prevent memory leaks
442
+ * - Comprehensive resource disposal logic
415
443
  */
416
444
  class LiquidFillerGroup {
417
445
  /**
418
- * 构造函数
419
- * @param models 单个或多个 THREE.Object3D
420
- * @param scene 场景
421
- * @param camera 相机
422
- * @param renderer 渲染器
423
- * @param defaultOptions 默认液体选项
424
- * @param clickThreshold 点击阈值,单位像素
446
+ * Constructor
447
+ * @param models Single or multiple THREE.Object3D
448
+ * @param scene Scene
449
+ * @param camera Camera
450
+ * @param renderer Renderer
451
+ * @param defaultOptions Default liquid options
452
+ * @param clickThreshold Click threshold in pixels
425
453
  */
426
454
  constructor(models, scene, camera, renderer, defaultOptions, clickThreshold = 10) {
427
455
  this.items = [];
428
456
  this.raycaster = new THREE__namespace.Raycaster();
429
457
  this.pointerDownPos = new THREE__namespace.Vector2();
430
458
  this.clickThreshold = 10;
431
- this.abortController = null; // 事件管理器
432
- /** pointerdown 记录位置 */
459
+ this.abortController = null; // Event manager
460
+ /** pointerdown record position */
433
461
  this.handlePointerDown = (event) => {
434
462
  this.pointerDownPos.set(event.clientX, event.clientY);
435
463
  };
436
- /** pointerup 判断点击空白,恢复原始材质 */
464
+ /** pointerup check click blank, restore original material */
437
465
  this.handlePointerUp = (event) => {
438
466
  const dx = event.clientX - this.pointerDownPos.x;
439
467
  const dy = event.clientY - this.pointerDownPos.y;
440
468
  const distance = Math.sqrt(dx * dx + dy * dy);
441
469
  if (distance > this.clickThreshold)
442
- return; // 拖拽不触发
443
- // 使用 renderer.domElement 的实际尺寸
470
+ return; // Do not trigger on drag
471
+ // Use renderer.domElement actual size
444
472
  const rect = this.renderer.domElement.getBoundingClientRect();
445
473
  const pointerNDC = new THREE__namespace.Vector2(((event.clientX - rect.left) / rect.width) * 2 - 1, -((event.clientY - rect.top) / rect.height) * 2 + 1);
446
474
  this.raycaster.setFromCamera(pointerNDC, this.camera);
447
- // 点击空白 -> 所有模型恢复
475
+ // Click blank -> Restore all models
448
476
  const intersectsAny = this.items.some(item => this.raycaster.intersectObject(item.model, true).length > 0);
449
477
  if (!intersectsAny) {
450
478
  this.restoreAll();
@@ -454,7 +482,7 @@ class LiquidFillerGroup {
454
482
  this.camera = camera;
455
483
  this.renderer = renderer;
456
484
  this.clickThreshold = clickThreshold;
457
- // 创建 AbortController 用于事件管理
485
+ // Create AbortController for event management
458
486
  this.abortController = new AbortController();
459
487
  const modelArray = Array.isArray(models) ? models : [models];
460
488
  modelArray.forEach(model => {
@@ -465,7 +493,7 @@ class LiquidFillerGroup {
465
493
  opacity: (_b = defaultOptions === null || defaultOptions === void 0 ? void 0 : defaultOptions.opacity) !== null && _b !== void 0 ? _b : 0.6,
466
494
  speed: (_c = defaultOptions === null || defaultOptions === void 0 ? void 0 : defaultOptions.speed) !== null && _c !== void 0 ? _c : 0.05,
467
495
  };
468
- // 保存原始材质
496
+ // Save original materials
469
497
  const originalMaterials = new Map();
470
498
  model.traverse(obj => {
471
499
  if (obj.isMesh) {
@@ -473,12 +501,12 @@ class LiquidFillerGroup {
473
501
  originalMaterials.set(mesh, mesh.material);
474
502
  }
475
503
  });
476
- // 边界检查:确保有材质可以保存
504
+ // Boundary check: ensure there are materials to save
477
505
  if (originalMaterials.size === 0) {
478
- console.warn('LiquidFillerGroup: 模型没有 Mesh 对象', model);
506
+ console.warn('LiquidFillerGroup: Model has no Mesh objects', model);
479
507
  return;
480
508
  }
481
- // 应用淡线框材质
509
+ // Apply faded wireframe material
482
510
  model.traverse(obj => {
483
511
  if (obj.isMesh) {
484
512
  const mesh = obj;
@@ -490,7 +518,7 @@ class LiquidFillerGroup {
490
518
  });
491
519
  }
492
520
  });
493
- // 创建液体 Mesh
521
+ // Create liquid Mesh
494
522
  const geometries = [];
495
523
  model.traverse(obj => {
496
524
  if (obj.isMesh) {
@@ -501,12 +529,12 @@ class LiquidFillerGroup {
501
529
  }
502
530
  });
503
531
  if (geometries.length === 0) {
504
- console.warn('LiquidFillerGroup: 模型没有几何体', model);
532
+ console.warn('LiquidFillerGroup: Model has no geometries', model);
505
533
  return;
506
534
  }
507
535
  const mergedGeometry = BufferGeometryUtils__namespace.mergeGeometries(geometries, false);
508
536
  if (!mergedGeometry) {
509
- console.error('LiquidFillerGroup: 几何体合并失败', model);
537
+ console.error('LiquidFillerGroup: Failed to merge geometries', model);
510
538
  return;
511
539
  }
512
540
  const material = new THREE__namespace.MeshPhongMaterial({
@@ -517,7 +545,7 @@ class LiquidFillerGroup {
517
545
  });
518
546
  const liquidMesh = new THREE__namespace.Mesh(mergedGeometry, material);
519
547
  this.scene.add(liquidMesh);
520
- // 设置 clippingPlane
548
+ // Set clippingPlane
521
549
  const clipPlane = new THREE__namespace.Plane(new THREE__namespace.Vector3(0, -1, 0), 0);
522
550
  const mat = liquidMesh.material;
523
551
  mat.clippingPlanes = [clipPlane];
@@ -528,41 +556,41 @@ class LiquidFillerGroup {
528
556
  clipPlane,
529
557
  originalMaterials,
530
558
  options,
531
- animationId: null // 初始化动画 ID
559
+ animationId: null // Initialize animation ID
532
560
  });
533
561
  }
534
562
  catch (error) {
535
- console.error('LiquidFillerGroup: 初始化模型失败', model, error);
563
+ console.error('LiquidFillerGroup: Failed to initialize model', model, error);
536
564
  }
537
565
  });
538
- // 使用 renderer.domElement 替代 window,使用 AbortController signal
566
+ // Use renderer.domElement instead of window, use AbortController signal
539
567
  const signal = this.abortController.signal;
540
568
  this.renderer.domElement.addEventListener('pointerdown', this.handlePointerDown, { signal });
541
569
  this.renderer.domElement.addEventListener('pointerup', this.handlePointerUp, { signal });
542
570
  }
543
571
  /**
544
- * 设置液位
545
- * @param models 单个模型或模型数组
546
- * @param percent 液位百分比 0~1
547
- */
572
+ * Set liquid level
573
+ * @param models Single model or array of models
574
+ * @param percent Liquid level percentage 0~1
575
+ */
548
576
  fillTo(models, percent) {
549
- // 边界检查
577
+ // Boundary check
550
578
  if (percent < 0 || percent > 1) {
551
- console.warn('LiquidFillerGroup: percent 必须在 0~1 之间', percent);
579
+ console.warn('LiquidFillerGroup: percent must be between 0 and 1', percent);
552
580
  percent = Math.max(0, Math.min(1, percent));
553
581
  }
554
582
  const modelArray = Array.isArray(models) ? models : [models];
555
583
  modelArray.forEach(model => {
556
584
  const item = this.items.find(i => i.model === model);
557
585
  if (!item) {
558
- console.warn('LiquidFillerGroup: 未找到模型', model);
586
+ console.warn('LiquidFillerGroup: Model not found', model);
559
587
  return;
560
588
  }
561
589
  if (!item.liquidMesh) {
562
- console.warn('LiquidFillerGroup: liquidMesh 已被释放', model);
590
+ console.warn('LiquidFillerGroup: liquidMesh already disposed', model);
563
591
  return;
564
592
  }
565
- // 取消之前的动画
593
+ // Cancel previous animation
566
594
  if (item.animationId !== null) {
567
595
  cancelAnimationFrame(item.animationId);
568
596
  item.animationId = null;
@@ -590,14 +618,14 @@ class LiquidFillerGroup {
590
618
  animate();
591
619
  }
592
620
  catch (error) {
593
- console.error('LiquidFillerGroup: fillTo 执行失败', model, error);
621
+ console.error('LiquidFillerGroup: fillTo execution failed', model, error);
594
622
  }
595
623
  });
596
624
  }
597
- /** 设置多个模型液位,percentList items 顺序对应 */
625
+ /** Set multiple model levels, percentList corresponds to items order */
598
626
  fillToAll(percentList) {
599
627
  if (percentList.length !== this.items.length) {
600
- console.warn(`LiquidFillerGroup: percentList 长度 (${percentList.length}) items 长度 (${this.items.length}) 不匹配`);
628
+ console.warn(`LiquidFillerGroup: percentList length (${percentList.length}) does not match items length (${this.items.length})`);
601
629
  }
602
630
  percentList.forEach((p, idx) => {
603
631
  if (idx < this.items.length) {
@@ -605,17 +633,17 @@ class LiquidFillerGroup {
605
633
  }
606
634
  });
607
635
  }
608
- /** 恢复单个模型原始材质并移除液体 */
636
+ /** Restore single model original material and remove liquid */
609
637
  restore(model) {
610
638
  const item = this.items.find(i => i.model === model);
611
639
  if (!item)
612
640
  return;
613
- // 取消动画
641
+ // Cancel animation
614
642
  if (item.animationId !== null) {
615
643
  cancelAnimationFrame(item.animationId);
616
644
  item.animationId = null;
617
645
  }
618
- // 恢复原始材质
646
+ // Restore original material
619
647
  item.model.traverse(obj => {
620
648
  if (obj.isMesh) {
621
649
  const mesh = obj;
@@ -624,7 +652,7 @@ class LiquidFillerGroup {
624
652
  mesh.material = original;
625
653
  }
626
654
  });
627
- // 释放液体 Mesh
655
+ // Dispose liquid Mesh
628
656
  if (item.liquidMesh) {
629
657
  this.scene.remove(item.liquidMesh);
630
658
  item.liquidMesh.geometry.dispose();
@@ -637,20 +665,20 @@ class LiquidFillerGroup {
637
665
  item.liquidMesh = null;
638
666
  }
639
667
  }
640
- /** 恢复所有模型 */
668
+ /** Restore all models */
641
669
  restoreAll() {
642
670
  this.items.forEach(item => this.restore(item.model));
643
671
  }
644
- /** 销毁方法,释放事件和资源 */
672
+ /** Dispose method, release events and resources */
645
673
  dispose() {
646
- // 先恢复所有模型
674
+ // Restore all models first
647
675
  this.restoreAll();
648
- // 使用 AbortController 一次性解绑所有事件
676
+ // Unbind all events at once using AbortController
649
677
  if (this.abortController) {
650
678
  this.abortController.abort();
651
679
  this.abortController = null;
652
680
  }
653
- // 清空 items
681
+ // Clear items
654
682
  this.items.length = 0;
655
683
  }
656
684
  }