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