@littlecarlito/blorktools 0.50.4 → 0.51.0

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 (114) hide show
  1. package/bin/cli.js +69 -0
  2. package/package.json +13 -7
  3. package/src/asset_debugger/axis-indicator/axis-indicator.css +6 -0
  4. package/src/asset_debugger/axis-indicator/axis-indicator.html +20 -0
  5. package/src/asset_debugger/axis-indicator/axis-indicator.js +822 -0
  6. package/src/asset_debugger/debugger-scene/debugger-scene.css +142 -0
  7. package/src/asset_debugger/debugger-scene/debugger-scene.html +80 -0
  8. package/src/asset_debugger/debugger-scene/debugger-scene.js +791 -0
  9. package/src/asset_debugger/header/header.css +73 -0
  10. package/src/asset_debugger/header/header.html +24 -0
  11. package/src/asset_debugger/header/header.js +224 -0
  12. package/src/asset_debugger/index.html +76 -0
  13. package/src/asset_debugger/landing-page/landing-page.css +396 -0
  14. package/src/asset_debugger/landing-page/landing-page.html +81 -0
  15. package/src/asset_debugger/landing-page/landing-page.js +611 -0
  16. package/src/asset_debugger/loading-splash/loading-splash.css +195 -0
  17. package/src/asset_debugger/loading-splash/loading-splash.html +22 -0
  18. package/src/asset_debugger/loading-splash/loading-splash.js +59 -0
  19. package/src/asset_debugger/loading-splash/preview-loading-splash.js +66 -0
  20. package/src/asset_debugger/main.css +14 -0
  21. package/src/asset_debugger/modals/examples-modal/examples-modal.css +41 -0
  22. package/src/asset_debugger/modals/examples-modal/examples-modal.html +18 -0
  23. package/src/asset_debugger/modals/examples-modal/examples-modal.js +111 -0
  24. package/src/asset_debugger/modals/examples-modal/examples.js +125 -0
  25. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.css +452 -0
  26. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.html +87 -0
  27. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.js +675 -0
  28. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.css +219 -0
  29. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.html +20 -0
  30. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.js +548 -0
  31. package/src/asset_debugger/modals/settings-modal/settings-modal.css +103 -0
  32. package/src/asset_debugger/modals/settings-modal/settings-modal.html +158 -0
  33. package/src/asset_debugger/modals/settings-modal/settings-modal.js +475 -0
  34. package/src/asset_debugger/panels/asset-panel/asset-panel.css +263 -0
  35. package/src/asset_debugger/panels/asset-panel/asset-panel.html +123 -0
  36. package/src/asset_debugger/panels/asset-panel/asset-panel.js +136 -0
  37. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.css +94 -0
  38. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.js +312 -0
  39. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.css +129 -0
  40. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.js +486 -0
  41. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.css +545 -0
  42. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.js +538 -0
  43. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.css +70 -0
  44. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.js +586 -0
  45. package/src/asset_debugger/panels/world-panel/world-panel.css +364 -0
  46. package/src/asset_debugger/panels/world-panel/world-panel.html +173 -0
  47. package/src/asset_debugger/panels/world-panel/world-panel.js +1891 -0
  48. package/src/asset_debugger/router.js +190 -0
  49. package/src/asset_debugger/util/animation/playback/animation-playback-controller.js +150 -0
  50. package/src/asset_debugger/util/animation/playback/animation-preview-controller.js +316 -0
  51. package/src/asset_debugger/util/animation/playback/css3d-bounce-controller.js +400 -0
  52. package/src/asset_debugger/util/animation/playback/css3d-reversal-controller.js +821 -0
  53. package/src/asset_debugger/util/animation/render/css3d-prerender-controller.js +696 -0
  54. package/src/asset_debugger/util/animation/render/debug-texture-factory.js +0 -0
  55. package/src/asset_debugger/util/animation/render/iframe2texture-render-controller.js +199 -0
  56. package/src/asset_debugger/util/animation/render/image2texture-prerender-controller.js +461 -0
  57. package/src/asset_debugger/util/animation/render/pbr-material-factory.js +82 -0
  58. package/src/asset_debugger/util/common.css +280 -0
  59. package/src/asset_debugger/util/data/animation-classifier.js +323 -0
  60. package/src/asset_debugger/util/data/duplicate-handler.js +20 -0
  61. package/src/asset_debugger/util/data/glb-buffer-manager.js +407 -0
  62. package/src/asset_debugger/util/data/glb-classifier.js +290 -0
  63. package/src/asset_debugger/util/data/html-formatter.js +76 -0
  64. package/src/asset_debugger/util/data/html-linter.js +276 -0
  65. package/src/asset_debugger/util/data/localstorage-manager.js +265 -0
  66. package/src/asset_debugger/util/data/mesh-html-manager.js +295 -0
  67. package/src/asset_debugger/util/data/string-serder.js +303 -0
  68. package/src/asset_debugger/util/data/texture-classifier.js +663 -0
  69. package/src/asset_debugger/util/data/upload/background-file-handler.js +292 -0
  70. package/src/asset_debugger/util/data/upload/dropzone-preview-controller.js +396 -0
  71. package/src/asset_debugger/util/data/upload/file-upload-manager.js +495 -0
  72. package/src/asset_debugger/util/data/upload/glb-file-handler.js +36 -0
  73. package/src/asset_debugger/util/data/upload/glb-preview-controller.js +317 -0
  74. package/src/asset_debugger/util/data/upload/lighting-file-handler.js +194 -0
  75. package/src/asset_debugger/util/data/upload/model-file-manager.js +104 -0
  76. package/src/asset_debugger/util/data/upload/texture-file-handler.js +166 -0
  77. package/src/asset_debugger/util/data/upload/zip-handler.js +686 -0
  78. package/src/asset_debugger/util/loaders/html2canvas-loader.js +107 -0
  79. package/src/asset_debugger/util/rig/bone-kinematics.js +403 -0
  80. package/src/asset_debugger/util/rig/rig-constraint-manager.js +618 -0
  81. package/src/asset_debugger/util/rig/rig-controller.js +612 -0
  82. package/src/asset_debugger/util/rig/rig-factory.js +628 -0
  83. package/src/asset_debugger/util/rig/rig-handle-factory.js +46 -0
  84. package/src/asset_debugger/util/rig/rig-label-factory.js +441 -0
  85. package/src/asset_debugger/util/rig/rig-mouse-handler.js +377 -0
  86. package/src/asset_debugger/util/rig/rig-state-manager.js +175 -0
  87. package/src/asset_debugger/util/rig/rig-tooltip-manager.js +267 -0
  88. package/src/asset_debugger/util/rig/rig-ui-factory.js +700 -0
  89. package/src/asset_debugger/util/scene/background-manager.js +284 -0
  90. package/src/asset_debugger/util/scene/camera-controller.js +243 -0
  91. package/src/asset_debugger/util/scene/css3d-debug-controller.js +406 -0
  92. package/src/asset_debugger/util/scene/css3d-frame-factory.js +113 -0
  93. package/src/asset_debugger/util/scene/css3d-scene-manager.js +529 -0
  94. package/src/asset_debugger/util/scene/glb-controller.js +208 -0
  95. package/src/asset_debugger/util/scene/lighting-manager.js +690 -0
  96. package/src/asset_debugger/util/scene/threejs-model-manager.js +437 -0
  97. package/src/asset_debugger/util/scene/threejs-preview-manager.js +207 -0
  98. package/src/asset_debugger/util/scene/threejs-preview-setup.js +478 -0
  99. package/src/asset_debugger/util/scene/threejs-scene-controller.js +286 -0
  100. package/src/asset_debugger/util/scene/ui-manager.js +107 -0
  101. package/src/asset_debugger/util/state/animation-state.js +128 -0
  102. package/src/asset_debugger/util/state/css3d-state.js +83 -0
  103. package/src/asset_debugger/util/state/glb-preview-state.js +31 -0
  104. package/src/asset_debugger/util/state/log-util.js +197 -0
  105. package/src/asset_debugger/util/state/scene-state.js +452 -0
  106. package/src/asset_debugger/util/state/threejs-state.js +54 -0
  107. package/src/asset_debugger/util/workers/lighting-worker.js +61 -0
  108. package/src/asset_debugger/util/workers/model-worker.js +109 -0
  109. package/src/asset_debugger/util/workers/texture-worker.js +54 -0
  110. package/src/asset_debugger/util/workers/worker-manager.js +212 -0
  111. package/src/asset_debugger/widgets/mesh-info-widget.js +280 -0
  112. package/src/index.html +261 -0
  113. package/src/index.js +8 -0
  114. package/vite.config.js +66 -0
@@ -0,0 +1,478 @@
1
+ import * as THREE from 'three';
2
+ import {
3
+ animationPreviewCamera,
4
+ animationPreviewRenderer,
5
+ animationPreviewScene,
6
+ previewPlane,
7
+ setAnimationPreviewCamera,
8
+ setAnimationPreviewRenderer,
9
+ setAnimationPreviewScene,
10
+ setPreviewPlane,
11
+ setPreviewRenderTarget
12
+ } from "../state/threejs-state";
13
+ import { showStatus } from '../../modals/html-editor-modal/html-editor-modal';
14
+ import { createMeshInfoPanel } from '../../widgets/mesh-info-widget';
15
+ import { createTextureFromIframe } from '../animation/render/iframe2texture-render-controller';
16
+ import { getState } from '../state/scene-state';
17
+ import { animatePreview } from '../animation/playback/animation-preview-controller';
18
+ import { setIsPreviewActive } from '../state/animation-state';
19
+
20
+ export function setupThreeJsScene(container, iframe, currentMeshId, createInfoPanel = true) {
21
+ try {
22
+ setAnimationPreviewScene(new THREE.Scene());
23
+ animationPreviewScene.background = new THREE.Color(0x303030);
24
+
25
+ const containerWidth = container.clientWidth;
26
+ const containerHeight = container.clientHeight;
27
+ const containerAspectRatio = containerWidth / containerHeight;
28
+
29
+ setAnimationPreviewCamera(new THREE.PerspectiveCamera(
30
+ 60, containerAspectRatio, 0.1, 1000
31
+ ));
32
+ animationPreviewCamera.position.z = 3;
33
+
34
+ setAnimationPreviewRenderer(new THREE.WebGLRenderer({
35
+ antialias: true,
36
+ preserveDrawingBuffer: true
37
+ }));
38
+ animationPreviewRenderer.setSize(containerWidth, containerHeight);
39
+ animationPreviewRenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
40
+ animationPreviewRenderer.setClearColor(0x303030);
41
+ animationPreviewRenderer.outputEncoding = THREE.sRGBEncoding;
42
+
43
+ animationPreviewRenderer.shadowMap.enabled = false;
44
+
45
+ const rendererCanvas = animationPreviewRenderer.domElement;
46
+ rendererCanvas.style.display = 'block';
47
+ rendererCanvas.style.width = '100%';
48
+ rendererCanvas.style.height = '100%';
49
+ container.appendChild(rendererCanvas);
50
+
51
+ setPreviewRenderTarget(iframe);
52
+
53
+ if (createInfoPanel) {
54
+ createMeshInfoPanel(container, currentMeshId);
55
+ }
56
+
57
+ createTextureFromIframe(iframe).then(texture => {
58
+ const state = getState();
59
+ const originalMesh = state.meshes && state.meshes[currentMeshId];
60
+ let geometry;
61
+
62
+ if (originalMesh && originalMesh.geometry) {
63
+ try {
64
+ geometry = originalMesh.geometry.clone();
65
+ console.log("Using original mesh geometry for preview");
66
+
67
+ const isPlaneOrRectangle = geometry.attributes.position.count <= 8;
68
+
69
+ let dominantPlane = 'xy';
70
+ let normalVector = new THREE.Vector3(0, 0, 1);
71
+ let upVector = new THREE.Vector3(0, 1, 0);
72
+
73
+ if (geometry.attributes.position) {
74
+ let minX = Infinity, maxX = -Infinity;
75
+ let minY = Infinity, maxY = -Infinity;
76
+ let minZ = Infinity, maxZ = -Infinity;
77
+
78
+ const positions = geometry.attributes.position;
79
+ const count = positions.count;
80
+
81
+ for (let i = 0; i < count; i++) {
82
+ const x = positions.getX(i);
83
+ const y = positions.getY(i);
84
+ const z = positions.getZ(i);
85
+
86
+ minX = Math.min(minX, x);
87
+ maxX = Math.max(maxX, x);
88
+ minY = Math.min(minY, y);
89
+ maxY = Math.max(maxY, y);
90
+ minZ = Math.min(minZ, z);
91
+ maxZ = Math.max(maxZ, z);
92
+ }
93
+
94
+ const rangeX = maxX - minX;
95
+ const rangeY = maxY - minY;
96
+ const rangeZ = maxZ - minZ;
97
+
98
+ console.log(`Mesh dimensions: X: ${rangeX.toFixed(2)}, Y: ${rangeY.toFixed(2)}, Z: ${rangeZ.toFixed(2)}`);
99
+
100
+ if (rangeZ < rangeX && rangeZ < rangeY) {
101
+ dominantPlane = 'xy';
102
+ normalVector = new THREE.Vector3(0, 0, 1);
103
+ upVector = new THREE.Vector3(0, 1, 0);
104
+ } else if (rangeY < rangeX && rangeY < rangeZ) {
105
+ dominantPlane = 'xz';
106
+ normalVector = new THREE.Vector3(0, 1, 0);
107
+ upVector = new THREE.Vector3(0, 0, 1);
108
+ } else {
109
+ dominantPlane = 'yz';
110
+ normalVector = new THREE.Vector3(1, 0, 0);
111
+ upVector = new THREE.Vector3(0, 1, 0);
112
+ }
113
+
114
+ if (geometry.index) {
115
+ try {
116
+ geometry.computeVertexNormals();
117
+ let normalSum = new THREE.Vector3(0, 0, 0);
118
+
119
+ if (geometry.attributes.normal) {
120
+ const normals = geometry.attributes.normal;
121
+ for (let i = 0; i < normals.count; i++) {
122
+ normalSum.x += normals.getX(i);
123
+ normalSum.y += normals.getY(i);
124
+ normalSum.z += normals.getZ(i);
125
+ }
126
+ normalSum.divideScalar(normals.count);
127
+ normalSum.normalize();
128
+
129
+ if (normalSum.length() > 0.5) {
130
+ normalVector = normalSum;
131
+ console.log(`Detected normal vector: (${normalVector.x.toFixed(2)}, ${normalVector.y.toFixed(2)}, ${normalVector.z.toFixed(2)})`);
132
+
133
+ if (Math.abs(normalVector.y) < 0.7) {
134
+ upVector = new THREE.Vector3(0, 1, 0);
135
+ upVector.sub(normalVector.clone().multiplyScalar(normalVector.dot(upVector)));
136
+ upVector.normalize();
137
+ } else {
138
+ upVector = new THREE.Vector3(0, 0, 1);
139
+ upVector.sub(normalVector.clone().multiplyScalar(normalVector.dot(upVector)));
140
+ upVector.normalize();
141
+ }
142
+ console.log(`Calculated up vector: (${upVector.x.toFixed(2)}, ${upVector.y.toFixed(2)}, ${upVector.z.toFixed(2)})`);
143
+ }
144
+ }
145
+ } catch (e) {
146
+ console.warn("Error computing normals:", e);
147
+ }
148
+ }
149
+
150
+ console.log(`Using plane: ${dominantPlane}, normal: (${normalVector.x.toFixed(2)}, ${normalVector.y.toFixed(2)}, ${normalVector.z.toFixed(2)})`);
151
+
152
+ const uvs = new Float32Array(count * 2);
153
+
154
+ const transformMatrix = new THREE.Matrix4();
155
+
156
+ const quaternion = new THREE.Quaternion().setFromUnitVectors(normalVector, new THREE.Vector3(0, 0, 1));
157
+ transformMatrix.makeRotationFromQuaternion(quaternion);
158
+
159
+ const tempVector = new THREE.Vector3();
160
+
161
+ for (let i = 0; i < count; i++) {
162
+ const x = positions.getX(i);
163
+ const y = positions.getY(i);
164
+ const z = positions.getZ(i);
165
+
166
+ tempVector.set(x, y, z);
167
+ tempVector.applyMatrix4(transformMatrix);
168
+
169
+ const transformedX = tempVector.x;
170
+ const transformedY = tempVector.y;
171
+
172
+ const u = (transformedX - minX) / Math.max(0.0001, rangeX);
173
+ const v = (transformedY - minY) / Math.max(0.0001, rangeY);
174
+
175
+ uvs[i * 2] = u;
176
+ uvs[i * 2 + 1] = v;
177
+ }
178
+
179
+ geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
180
+ console.log("Created custom UVs for rectangle/plane geometry");
181
+
182
+ geometry.userData = geometry.userData || {};
183
+ geometry.userData.dominantPlane = dominantPlane;
184
+ geometry.userData.normalVector = normalVector;
185
+ geometry.userData.upVector = upVector;
186
+ }
187
+ } catch (e) {
188
+ console.warn("Could not clone original mesh geometry, falling back to cube", e);
189
+ geometry = new THREE.BoxGeometry(1, 1, 1, 1, 1, 1);
190
+ }
191
+ } else {
192
+ console.log("No mesh found with ID " + currentMeshId + ", using cube geometry");
193
+ geometry = new THREE.BoxGeometry(1, 1, 1, 1, 1, 1);
194
+ }
195
+
196
+ const material = new THREE.MeshBasicMaterial({
197
+ map: texture,
198
+ side: THREE.DoubleSide,
199
+ transparent: true,
200
+ depthWrite: true,
201
+ fog: false
202
+ });
203
+
204
+ setPreviewPlane(new THREE.Mesh(geometry, material));
205
+
206
+ previewPlane.frustumCulled = false;
207
+
208
+ animationPreviewScene.add(previewPlane);
209
+
210
+ if (geometry.userData && geometry.userData.normalVector) {
211
+ const normalVector = geometry.userData.normalVector;
212
+ const upVector = geometry.userData.upVector;
213
+ const dominantPlane = geometry.userData.dominantPlane;
214
+
215
+ const cameraPosition = normalVector.clone().multiplyScalar(2);
216
+ animationPreviewCamera.position.copy(cameraPosition);
217
+
218
+ animationPreviewCamera.lookAt(0, 0, 0);
219
+
220
+ animationPreviewCamera.up.copy(upVector);
221
+
222
+ const lookAtMatrix = new THREE.Matrix4();
223
+ lookAtMatrix.lookAt(
224
+ new THREE.Vector3(0, 0, 0),
225
+ normalVector.clone().negate(),
226
+ upVector
227
+ );
228
+
229
+ const rotation = new THREE.Quaternion();
230
+ rotation.setFromRotationMatrix(lookAtMatrix);
231
+ previewPlane.quaternion.copy(rotation);
232
+
233
+ console.log(`Camera positioned based on detected normal and up vectors`);
234
+ } else if (dominantPlane) {
235
+ if (dominantPlane === 'xy') {
236
+ animationPreviewCamera.position.set(0, 0, 3);
237
+ animationPreviewCamera.lookAt(0, 0, 0);
238
+ previewPlane.rotation.set(0, 0, 0);
239
+ } else if (dominantPlane === 'xz') {
240
+ animationPreviewCamera.position.set(0, 3, 0);
241
+ animationPreviewCamera.lookAt(0, 0, 0);
242
+ previewPlane.rotation.set(-Math.PI/2, 0, 0);
243
+ } else {
244
+ animationPreviewCamera.position.set(3, 0, 0);
245
+ animationPreviewCamera.lookAt(0, 0, 0);
246
+ previewPlane.rotation.set(0, Math.PI/2, 0);
247
+ }
248
+
249
+ console.log(`Camera positioned to view the ${dominantPlane} plane`);
250
+ } else {
251
+ animationPreviewCamera.position.z = 3;
252
+
253
+ previewPlane.rotation.x = Math.PI / 10;
254
+ previewPlane.rotation.y = Math.PI / 6;
255
+ }
256
+
257
+ setupOrbitControls(animationPreviewCamera, animationPreviewRenderer.domElement)
258
+ .then(controls => {
259
+ animatePreview();
260
+
261
+ showStatus('Preview ready. Use +/- keys to zoom in/out', 'success');
262
+
263
+ const handleKeydown = (event) => {
264
+ if (!isPreviewActive) return;
265
+
266
+ const controls = animationPreviewCamera.userData.controls;
267
+ if (!controls) return;
268
+
269
+ const zoomSpeed = 0.2;
270
+
271
+ switch (event.key) {
272
+ case '+':
273
+ case '=':
274
+ controls.dollyIn(1 + zoomSpeed);
275
+ controls.update();
276
+ break;
277
+ case '-':
278
+ case '_':
279
+ controls.dollyOut(1 + zoomSpeed);
280
+ controls.update();
281
+ break;
282
+ }
283
+ };
284
+
285
+ document.addEventListener('keydown', handleKeydown);
286
+
287
+ animationPreviewCamera.userData.keyHandler = handleKeydown;
288
+ })
289
+ .catch(error => {
290
+ console.error('Failed to setup orbit controls:', error);
291
+ animatePreview();
292
+ });
293
+ }).catch(error => {
294
+ console.error('Error creating texture from iframe:', error);
295
+ showStatus('Error creating texture from HTML: ' + error.message, 'error');
296
+ animatePreview();
297
+ });
298
+ } catch (error) {
299
+ console.error('Error setting up Three.js scene:', error);
300
+ showStatus('Error in Three.js scene setup: ' + error.message, 'error');
301
+ }
302
+ }
303
+
304
+ export function cleanupThreeJsScene(config = {}) {
305
+ const {
306
+ scene,
307
+ camera,
308
+ renderer,
309
+ objects = [],
310
+ domElements = [],
311
+ eventCleanupCallbacks = [],
312
+ additionalCleanup = {}
313
+ } = config;
314
+
315
+ objects.forEach(object => {
316
+ if (object) {
317
+ disposeObject3D(object);
318
+ }
319
+ });
320
+
321
+ if (scene) {
322
+ while (scene.children.length > 0) {
323
+ const object = scene.children[0];
324
+ scene.remove(object);
325
+ disposeObject3D(object);
326
+ }
327
+ }
328
+
329
+ if (renderer) {
330
+ try {
331
+ if (typeof renderer.dispose === 'function') {
332
+ renderer.dispose();
333
+ }
334
+ if (renderer.domElement && renderer.domElement.parentElement) {
335
+ renderer.domElement.parentElement.removeChild(renderer.domElement);
336
+ }
337
+ } catch (e) {
338
+ console.log('Renderer cleanup error (non-critical):', e);
339
+ }
340
+ }
341
+
342
+ domElements.forEach(element => {
343
+ if (element) {
344
+ cleanupDOMElement(element);
345
+ }
346
+ });
347
+
348
+ eventCleanupCallbacks.forEach(callback => {
349
+ try {
350
+ if (typeof callback === 'function') {
351
+ callback();
352
+ }
353
+ } catch (e) {
354
+ console.log('Event cleanup callback error (non-critical):', e);
355
+ }
356
+ });
357
+
358
+ if (additionalCleanup.textures) {
359
+ additionalCleanup.textures.forEach(texture => {
360
+ if (texture && texture.dispose) {
361
+ texture.dispose();
362
+ }
363
+ });
364
+ }
365
+
366
+ if (additionalCleanup.frameBuffer) {
367
+ additionalCleanup.frameBuffer.forEach(frame => {
368
+ if (frame && frame.texture) {
369
+ frame.texture.dispose();
370
+ }
371
+ });
372
+ }
373
+
374
+ if (additionalCleanup.animationFrames) {
375
+ additionalCleanup.animationFrames.forEach(frameId => {
376
+ if (frameId !== null && frameId !== undefined) {
377
+ cancelAnimationFrame(frameId);
378
+ }
379
+ });
380
+ }
381
+ }
382
+
383
+ function disposeObject3D(object) {
384
+ if (!object) return;
385
+
386
+ try {
387
+ if (object.geometry) {
388
+ object.geometry.dispose();
389
+ }
390
+
391
+ if (object.material) {
392
+ if (Array.isArray(object.material)) {
393
+ object.material.forEach(material => disposeMaterial(material));
394
+ } else {
395
+ disposeMaterial(object.material);
396
+ }
397
+ }
398
+
399
+ if (object.element && object.element.parentNode) {
400
+ cleanupDOMElement(object.element);
401
+ }
402
+
403
+ if (object.children) {
404
+ [...object.children].forEach(child => {
405
+ object.remove(child);
406
+ disposeObject3D(child);
407
+ });
408
+ }
409
+ } catch (e) {
410
+ console.log('Object3D disposal error (non-critical):', e);
411
+ }
412
+ }
413
+
414
+ function disposeMaterial(material) {
415
+ if (!material) return;
416
+
417
+ try {
418
+ Object.keys(material).forEach(key => {
419
+ const value = material[key];
420
+ if (value && value.isTexture) {
421
+ value.dispose();
422
+ }
423
+ });
424
+
425
+ material.dispose();
426
+ } catch (e) {
427
+ console.log('Material disposal error (non-critical):', e);
428
+ }
429
+ }
430
+
431
+ function cleanupDOMElement(element) {
432
+ if (!element) return;
433
+
434
+ try {
435
+ if (element.tagName === 'IFRAME' && element.contentDocument) {
436
+ element.contentDocument.open();
437
+ element.contentDocument.write('');
438
+ element.contentDocument.close();
439
+ }
440
+
441
+ if (element.parentNode) {
442
+ element.parentNode.removeChild(element);
443
+ }
444
+ } catch (e) {
445
+ console.log('DOM element cleanup error (non-critical):', e);
446
+ }
447
+ }
448
+
449
+ function setupOrbitControls(camera, domElement) {
450
+ return new Promise((resolve, reject) => {
451
+ try {
452
+ import('three/examples/jsm/controls/OrbitControls.js')
453
+ .then(module => {
454
+ const { OrbitControls } = module;
455
+
456
+ const controls = new OrbitControls(camera, domElement);
457
+ controls.enableDamping = true;
458
+ controls.dampingFactor = 0.2;
459
+ controls.rotateSpeed = 0.5;
460
+ controls.minDistance = 0.5;
461
+ controls.maxDistance = 20;
462
+ controls.zoomSpeed = 1.2;
463
+
464
+ camera.userData = camera.userData || {};
465
+ camera.userData.controls = controls;
466
+
467
+ resolve(controls);
468
+ })
469
+ .catch(error => {
470
+ console.error('Error loading OrbitControls:', error);
471
+ reject(error);
472
+ });
473
+ } catch (error) {
474
+ console.error('Error setting up OrbitControls:', error);
475
+ reject(error);
476
+ }
477
+ });
478
+ }