@littlecarlito/blorktools 0.50.3 → 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,529 @@
1
+ import * as THREE from 'three';
2
+ import {
3
+ isReversingAnimations,
4
+ pushAnimationStack,
5
+ setCapturingAnimations,
6
+ setReversingAnimation,
7
+ resetCurrentAniamtionBatch,
8
+ currentAnimationBatch,
9
+ lastBatchTime,
10
+ resetReverseAnimationFrameId,
11
+ reverseAnimationFrameId,
12
+ animationStack,
13
+ resetAnimationState
14
+ } from '../state/css3d-state';
15
+ import { createMeshInfoPanel } from '../../widgets/mesh-info-widget';
16
+ import {
17
+ setAnimationCss3dRenderer,
18
+ setAnimationCss3dScene,
19
+ setAnimationCss3dObject,
20
+ setAnimationPreviewCamera,
21
+ setPreviewRenderTarget,
22
+ animationPreviewCamera
23
+ } from '../state/threejs-state';
24
+ import { showStatus } from '../../modals/html-editor-modal/html-editor-modal';
25
+ import { playNextReverseAnimation } from '../animation/playback/css3d-reversal-controller';
26
+ import { setupBounceAnimationTracking } from '../animation/playback/css3d-bounce-controller';
27
+ import { isPreviewActive } from '../state/animation-state';
28
+ import { calculateMeshTransform } from './css3d-frame-factory';
29
+
30
+ /**
31
+ * Setup the CSS3D scene
32
+ * @param {HTMLElement} container - The container element for the renderers
33
+ * @param {HTMLIFrameElement} iframe - The iframe containing the HTML content
34
+ * @param {number} currentMeshId - The ID of the current mesh
35
+ * @param {boolean} createInfoPanel - Whether to create the info panel
36
+ */
37
+ export function setupCSS3DScene(container, iframe, CSS3DRenderer, CSS3DObject, currentMeshId, createInfoPanel = true) {
38
+ try {
39
+ console.log('Setting up CSS3D scene with container:', container);
40
+
41
+ resetAnimationState();
42
+
43
+ // Clear any existing content
44
+ container.innerHTML = '';
45
+
46
+ // Basic variables
47
+ const userHtml = document.getElementById('html-editor-textarea').value || '';
48
+
49
+ // Panel size - use a single panel instead of a cube
50
+ const panelWidth = 500;
51
+ const panelHeight = 400;
52
+
53
+ // Setup camera with proper distance to see the panel
54
+ const camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 1, 10000);
55
+ camera.position.set(0, 0, 700); // Position to see panel straight on
56
+
57
+ // Create CSS3D scene
58
+ const scene = new THREE.Scene();
59
+ scene.background = new THREE.Color(0x303030); // Dark gray background like Unreal Editor
60
+
61
+ // Create CSS3D renderer
62
+ const renderer = new CSS3DRenderer();
63
+ renderer.setSize(container.clientWidth, container.clientHeight);
64
+ renderer.domElement.style.position = 'absolute';
65
+ renderer.domElement.style.top = '0';
66
+ container.appendChild(renderer.domElement);
67
+ // Create info panel if requested
68
+ if (createInfoPanel) {
69
+ createMeshInfoPanel(container, currentMeshId);
70
+ }
71
+
72
+ // Get settings for this mesh
73
+ const settings = getHtmlSettingsForMesh(currentMeshId);
74
+ const playbackSpeed = settings.playbackSpeed || 1.0;
75
+
76
+ // Function to create HTML content - simplified to avoid layout warnings
77
+ const wrapContent = (content) => {
78
+ // Generate a unique ID for this iframe instance
79
+ const uniqueFrameId = 'frame_' + Date.now() + '_' + Math.floor(Math.random() * 1000000);
80
+
81
+ // Process content to handle typical variable declarations
82
+ // Find script tags and wrap their contents in a function to avoid global variable redeclarations
83
+ let processedContent = content;
84
+
85
+ // Find all script tags and wrap their contents in a closure to avoid redeclaration issues
86
+ processedContent = processedContent.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, (match, scriptContent) => {
87
+ // Wrap script content in an IIFE to create a new scope each time
88
+ return `<script>
89
+ (function() {
90
+ // Create a new scope for variables
91
+ ${scriptContent}
92
+ })();
93
+ </script>`;
94
+ });
95
+
96
+ return `<!DOCTYPE html>
97
+ <html>
98
+ <head>
99
+ <style>
100
+ html, body {
101
+ margin: 0;
102
+ padding: 0;
103
+ width: 100%;
104
+ height: 100%;
105
+ overflow: hidden;
106
+ box-sizing: border-box;
107
+ }
108
+ body {
109
+ background-color: white;
110
+ color: #333;
111
+ font-family: Arial, sans-serif;
112
+ padding: 10px;
113
+ display: flex;
114
+ flex-direction: column;
115
+ }
116
+ .content {
117
+ flex: 1;
118
+ overflow: hidden;
119
+ padding: 5px;
120
+ position: relative;
121
+ width: calc(100% - 10px);
122
+ }
123
+
124
+ /* Add a border if enabled */
125
+ ${window.showPreviewBorders ?
126
+ `body { border: 5px solid #3498db; }` :
127
+ ''}
128
+
129
+ /* Control animation speed - apply to all animations */
130
+ * {
131
+ animation-duration: ${1.0 / playbackSpeed}s !important;
132
+ transition-duration: ${1.0 / playbackSpeed}s !important;
133
+ }
134
+
135
+ /* Ensure content doesn't overflow */
136
+ .content > * {
137
+ max-width: 100%;
138
+ box-sizing: border-box;
139
+ }
140
+
141
+ /* Override any styles that might cause horizontal scrollbars */
142
+ .content div, .content p, .content span, .content img {
143
+ max-width: 100%;
144
+ }
145
+ </style>
146
+ </head>
147
+ <body>
148
+ <div class="content">${processedContent}</div>
149
+ </body>
150
+ </html>`;
151
+ };
152
+
153
+ // Store the wrapContent function at the global file scope to make it available to other functions
154
+ setupCSS3DScene.wrapContent = wrapContent;
155
+
156
+ // Create a DOM container to hold the iframe temporarily
157
+ const tempContainer = document.createElement('div');
158
+ tempContainer.style.position = 'absolute';
159
+ tempContainer.style.left = '-9999px'; // Off-screen
160
+ tempContainer.style.top = '0';
161
+ tempContainer.style.zIndex = '-1'; // Behind everything
162
+ tempContainer.style.opacity = '0.01'; // Almost invisible, but still rendered
163
+ tempContainer.style.pointerEvents = 'none'; // Don't interact with user input
164
+ document.body.appendChild(tempContainer);
165
+
166
+ // Create a single iframe for the panel
167
+ let element = document.createElement('iframe');
168
+ element.id = 'css3d-panel-iframe';
169
+ element.style.width = `${panelWidth}px`;
170
+ element.style.height = `${panelHeight}px`;
171
+ element.style.border = 'none'; // Remove border - we'll add it in the content if needed
172
+ element.style.borderRadius = '5px';
173
+ element.style.backgroundColor = 'white';
174
+ element.style.overflow = 'hidden'; // Prevent scrollbars
175
+ element.style.boxSizing = 'border-box';
176
+
177
+ // Add the iframe to DOM first
178
+ tempContainer.appendChild(element);
179
+
180
+ // Create a CSS3D object with the iframe
181
+ const object = new CSS3DObject(element);
182
+
183
+ // Add to scene
184
+ scene.add(object);
185
+
186
+ // Store references for cleanup
187
+ setAnimationCss3dScene(scene);
188
+ setAnimationCss3dRenderer(renderer);
189
+ setAnimationPreviewCamera(camera);
190
+
191
+ // Store for replay
192
+ setPreviewRenderTarget(element);
193
+ setAnimationCss3dObject(object);
194
+
195
+ // Write content to the iframe after a brief delay
196
+ setTimeout(() => {
197
+ try {
198
+ if (element.contentDocument) {
199
+ element.contentDocument.open();
200
+ element.contentDocument.write(wrapContent(userHtml));
201
+ element.contentDocument.close();
202
+
203
+ // Get detected animation duration from pre-render (animationDuration is in milliseconds)
204
+ import('../state/animation-state').then(module => {
205
+ const { animationDuration, isAnimationFinite } = module;
206
+
207
+ // Get animation type from dropdown
208
+ const animationTypeSelect = document.getElementById('html-animation-type');
209
+ const animationType = animationTypeSelect ? animationTypeSelect.value : 'play';
210
+
211
+ // Only set up restart timer if:
212
+ // 1. Animation is finite and has a duration
213
+ // 2. Animation type is set to "loop" or "bounce"
214
+ if (isAnimationFinite && animationDuration > 0 && (animationType === 'loop' || animationType === 'bounce')) {
215
+ console.log(`Setting up CSS3D iframe restart timer for ${animationDuration}ms animation with animation type: ${animationType}`);
216
+
217
+ // Calculate actual duration based on playback speed
218
+ const actualDuration = animationDuration / playbackSpeed;
219
+
220
+ // For bounce, we need to track animations
221
+ const isBounceMode = animationType === 'bounce';
222
+
223
+ // Create repeating timer to reload the iframe content
224
+ const restartTimer = setInterval(() => {
225
+ if (!isPreviewActive) {
226
+ // Clean up timer if preview is no longer active
227
+ clearInterval(restartTimer);
228
+ return;
229
+ }
230
+
231
+ // Handle bounce animation differently
232
+ if (isBounceMode) {
233
+ if (!isReversingAnimations) {
234
+ // We've reached the end of the forward animation
235
+ console.debug('Bounce end reached popping animation stack');
236
+ setReversingAnimation(true);
237
+ setCapturingAnimations(false);
238
+
239
+ // If we have animations in the stack, play them in reverse
240
+ if (animationStack.length > 0) {
241
+ console.debug(`Animation stack contains ${animationStack.length} items to reverse`);
242
+ try {
243
+ if (element.contentDocument) {
244
+ // First, ensure any pending animation batch is committed to the stack
245
+ if (currentAnimationBatch.length > 0) {
246
+ console.debug(`Committing final batch of ${currentAnimationBatch.length} animations to stack before reversal`);
247
+ pushAnimationStack({
248
+ type: 'batch',
249
+ animations: [...currentAnimationBatch],
250
+ time: lastBatchTime || Date.now()
251
+ });
252
+ resetCurrentAniamtionBatch();
253
+ }
254
+
255
+ // Set all animations to pause at end point
256
+ const animElements = element.contentDocument.querySelectorAll('[style*="animation"]');
257
+ animElements.forEach(el => {
258
+ el.style.animationPlayState = 'paused';
259
+ });
260
+
261
+ // Set all transitions to pause too
262
+ const transElements = element.contentDocument.querySelectorAll('[style*="transition"]');
263
+ transElements.forEach(el => {
264
+ el.style.transitionProperty = 'none';
265
+ });
266
+
267
+ // Start the first reverse animation
268
+ playNextReverseAnimation(element);
269
+ }
270
+ } catch (err) {
271
+ console.error('Error preparing for reverse animations:', err);
272
+ }
273
+ } else {
274
+ // Stack is empty, odd case
275
+ console.debug('Animation stack is empty, restarting cycle');
276
+ setReversingAnimation(false);
277
+ setCapturingAnimations(true);
278
+ resetAndRestartAnimationCycle(element, userHtml);
279
+ }
280
+ } else {
281
+ // We've completed one animation in reverse, continue with the next
282
+ if (animationStack.length > 0) {
283
+ // Play the next animation in reverse
284
+ playNextReverseAnimation(element);
285
+ } else {
286
+ // No more animations to reverse, cycle complete
287
+ console.debug('Animation rewind successful');
288
+ setReversingAnimation(false);
289
+ setCapturingAnimations(true);
290
+ resetAndRestartAnimationCycle(element, userHtml);
291
+ }
292
+ }
293
+ } else {
294
+ // Normal loop behavior - just restart the content
295
+ console.log('Restarting CSS3D preview iframe content');
296
+
297
+ // Reload the iframe content to restart all animations from the beginning
298
+ try {
299
+ if (element.contentDocument) {
300
+ // Simply rewrite the content in the existing iframe
301
+ element.contentDocument.open();
302
+ element.contentDocument.write(wrapContent(userHtml));
303
+ element.contentDocument.close();
304
+ }
305
+ } catch (err) {
306
+ console.error('Error reloading iframe content:', err);
307
+ }
308
+ }
309
+ }, actualDuration);
310
+
311
+ // Store timer reference for cleanup
312
+ element.restartTimer = restartTimer;
313
+
314
+ // For bounce mode, set up initial animation tracking
315
+ if (isBounceMode) {
316
+ setTimeout(() => {
317
+ setupBounceAnimationTracking(element);
318
+ }, 50);
319
+ }
320
+ } else {
321
+ console.log('No finite animation duration detected or using Play animation mode, not setting up restart timer');
322
+ }
323
+ });
324
+ }
325
+ } catch (err) {
326
+ console.error('Error writing content to iframe:', err);
327
+ }
328
+ }, 50);
329
+
330
+ // Set up OrbitControls
331
+ import('three/examples/jsm/controls/OrbitControls.js').then(module => {
332
+ const { OrbitControls } = module;
333
+
334
+ // Create controls
335
+ const controls = new OrbitControls(camera, renderer.domElement);
336
+ controls.enableDamping = true;
337
+ controls.dampingFactor = 0.2;
338
+ controls.rotateSpeed = 0.5;
339
+ controls.minDistance = 100; // CSS3D needs larger distances
340
+ controls.maxDistance = 2000;
341
+ controls.zoomSpeed = 1.2;
342
+
343
+ // Initial look at origin
344
+ camera.lookAt(0, 0, 0);
345
+
346
+ // Animation loop
347
+ function animate() {
348
+ if (!isPreviewActive) {
349
+ return;
350
+ }
351
+
352
+ requestAnimationFrame(animate);
353
+
354
+ // Update controls
355
+ controls.update();
356
+
357
+ // Render scene
358
+ renderer.render(scene, camera);
359
+ }
360
+
361
+ // Start animation loop
362
+ animate();
363
+
364
+ // Show success status
365
+ showStatus('CSS3D preview ready. Use +/- keys to zoom in/out', 'success');
366
+
367
+ // Add keyboard shortcuts for zooming
368
+ const handleKeydown = (event) => {
369
+ if (!isPreviewActive) return;
370
+
371
+ // Get current controls - they should be attached to the camera by this point
372
+ const controls = animationPreviewCamera.userData.controls;
373
+ if (!controls) return;
374
+
375
+ const zoomSpeed = 0.2; // How fast to zoom with keyboard
376
+
377
+ switch (event.key) {
378
+ case '+':
379
+ case '=': // Common + key without shift
380
+ // Zoom in - decrease distance to target
381
+ controls.dollyIn(1 + zoomSpeed);
382
+ controls.update();
383
+ break;
384
+ case '-':
385
+ case '_': // Common - key with shift
386
+ // Zoom out - increase distance to target
387
+ controls.dollyOut(1 + zoomSpeed);
388
+ controls.update();
389
+ break;
390
+ }
391
+ };
392
+
393
+ // Register keyboard handler
394
+ document.addEventListener('keydown', handleKeydown);
395
+
396
+ // Store for cleanup
397
+ animationPreviewCamera.userData.keyHandler = handleKeydown;
398
+ }).catch(error => {
399
+ console.error('Error loading OrbitControls:', error);
400
+ showStatus('Error loading 3D controls: ' + error.message, 'error');
401
+ return false;
402
+ });
403
+
404
+ // Success
405
+ return true;
406
+ } catch (error) {
407
+ console.error('Error in setupCSS3DScene:', error);
408
+ showStatus('Error creating 3D view: ' + error.message, 'error');
409
+ return false;
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Reset and restart the animation cycle with fresh content
415
+ * @param {HTMLIFrameElement} iframe - The iframe to reset
416
+ * @param {string} html - The HTML content to reload
417
+ */
418
+ function resetAndRestartAnimationCycle(iframe, html) {
419
+ console.debug('Restarting CSS3D bounce animation cycle');
420
+ try {
421
+ // Access the wrapContent function from the global scope
422
+ const wrapContentFunc = setupCSS3DScene.wrapContent;
423
+
424
+ if (!wrapContentFunc) {
425
+ console.error('wrapContent function not available, cannot restart animation cycle');
426
+ return;
427
+ }
428
+
429
+ if (iframe.contentDocument) {
430
+ // Cancel any pending animation operations
431
+ if (reverseAnimationFrameId) {
432
+ cancelAnimationFrame(reverseAnimationFrameId);
433
+ resetReverseAnimationFrameId();
434
+ }
435
+
436
+ // Clean up the mutation observer if it exists
437
+ if (iframe.mutationObserver) {
438
+ iframe.mutationObserver.disconnect();
439
+ iframe.mutationObserver = null;
440
+ }
441
+
442
+ // First, clear all content to a blank slate
443
+ iframe.contentDocument.open();
444
+ iframe.contentDocument.write(`
445
+ <!DOCTYPE html>
446
+ <html>
447
+ <head>
448
+ <style>
449
+ html, body {
450
+ margin: 0;
451
+ padding: 0;
452
+ width: 100%;
453
+ height: 100%;
454
+ overflow: hidden;
455
+ background-color: white;
456
+ }
457
+ </style>
458
+ </head>
459
+ <body></body>
460
+ </html>
461
+ `);
462
+ iframe.contentDocument.close();
463
+
464
+ // Give a brief moment to ensure cleanup
465
+ setTimeout(() => {
466
+ resetAnimationState();
467
+
468
+ // Now reload the real content
469
+ iframe.contentDocument.open();
470
+ iframe.contentDocument.write(wrapContentFunc(html));
471
+ iframe.contentDocument.close();
472
+
473
+ // Set up animation tracking for the new cycle
474
+ setTimeout(() => {
475
+ setupBounceAnimationTracking(iframe);
476
+ }, 50);
477
+ }, 100); // Longer timeout for better cleanup
478
+ }
479
+ } catch (err) {
480
+ console.error('Error restarting animation cycle:', err);
481
+ }
482
+ }
483
+
484
+ export function cleanupCSS3D(targetElement = null) {
485
+ resetAnimationState();
486
+
487
+ if (reverseAnimationFrameId) {
488
+ cancelAnimationFrame(reverseAnimationFrameId);
489
+ resetReverseAnimationFrameId();
490
+ }
491
+
492
+ const iframe = targetElement || document.getElementById('css3d-panel-iframe');
493
+ if (iframe) {
494
+ if (iframe.restartTimer) {
495
+ clearInterval(iframe.restartTimer);
496
+ iframe.restartTimer = null;
497
+ }
498
+
499
+ if (iframe._animationStartHandler && iframe.contentDocument) {
500
+ iframe.contentDocument.removeEventListener('animationstart', iframe._animationStartHandler);
501
+ iframe._animationStartHandler = null;
502
+ }
503
+
504
+ if (iframe._transitionStartHandler && iframe.contentDocument) {
505
+ iframe.contentDocument.removeEventListener('transitionstart', iframe._transitionStartHandler);
506
+ iframe._transitionStartHandler = null;
507
+ }
508
+
509
+ if (iframe.mutationObserver) {
510
+ iframe.mutationObserver.disconnect();
511
+ iframe.mutationObserver = null;
512
+ }
513
+ }
514
+ }
515
+
516
+ export function addFrameToScene(frame, scene, mesh, frameConfig) {
517
+ const { realWidth, realHeight, frameWidth, frameHeight, offsetDistance = 0.001 } = frameConfig;
518
+
519
+ const transform = calculateMeshTransform(mesh, offsetDistance);
520
+ frame.position.copy(transform.position);
521
+ frame.rotation.copy(transform.rotation);
522
+ frame.quaternion.copy(transform.quaternion);
523
+
524
+ const scaleX = realWidth / frameWidth;
525
+ const scaleY = realHeight / frameHeight;
526
+ frame.scale.set(scaleX, scaleY, 1);
527
+
528
+ scene.add(frame);
529
+ }