@chocozhang/three-model-render 1.0.3 → 1.0.5

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