@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,696 @@
1
+ import { showStatus } from "../../../modals/html-editor-modal/html-editor-modal";
2
+ import { createMeshInfoPanel } from "../../../widgets/mesh-info-widget";
3
+ import { updateMeshTexture } from "../playback/animation-playback-controller";
4
+ import {
5
+ animationCaptureStartTime,
6
+ animationDuration,
7
+ animationPlaybackStartTime,
8
+ finalProgressAnimation,
9
+ finalProgressDuration,
10
+ finalProgressStartTime,
11
+ isAnimationFinite,
12
+ isPreviewActive,
13
+ preRenderedFrames,
14
+ preRenderingInProgress,
15
+ preRenderMaxDuration,
16
+ setAnimationCaptureStartTime,
17
+ setAnimationDuration,
18
+ setAnimationPlaybackStartTime,
19
+ setFinalProgressAnimation,
20
+ setFinalProgressStartTime,
21
+ setIsAnimationFinite,
22
+ setIsPreviewAnimationPaused,
23
+ setPreRenderedFrames,
24
+ setPreRenderingInProgress
25
+ } from "../../state/animation-state";
26
+ import { logAnimationAnalysisReport } from "../../state/log-util";
27
+ import { injectUnifiedAnimationDetectionScript } from "../../data/animation-classifier";
28
+
29
+ /**
30
+ * Start pre-rendering for CSS3D content
31
+ * @param {HTMLIFrameElement} iframe - The iframe containing the content
32
+ * @param {Function} callback - Function to call when pre-rendering is complete
33
+ * @param {HTMLElement} progressBar - Optional progress bar element to update
34
+ * @param {Object} settings - Optional settings object
35
+ * @param {THREE.Mesh} previewPlane - The mesh to apply textures to
36
+ */
37
+ export function startCss3dPreRendering(iframe, callback, progressBar = null, settings = null, previewPlane = null) {
38
+ if (!iframe) {
39
+ console.error('No iframe provided for CSS3D pre-rendering');
40
+ if (callback) callback();
41
+ return;
42
+ }
43
+
44
+ // Reset and initialize state
45
+ setPreRenderingInProgress(true);
46
+ setPreRenderedFrames([]);
47
+
48
+ // Tracking variables
49
+ let domSnapshotFrames = [];
50
+ const preRenderStartTime = Date.now();
51
+
52
+ // Progress tracking
53
+ let lastProgressUpdate = 0;
54
+ let progressUpdateInterval = 100; // Update progress every 100ms
55
+ let maxProgressBeforeFinalAnimation = 92; // Cap progress at this value until final animation
56
+ setFinalProgressAnimation(false);
57
+ setFinalProgressStartTime(0);
58
+
59
+ // Analysis metrics tracking
60
+ let loopDetected = false;
61
+ let endDetected = false;
62
+ let analysisMetrics = {};
63
+ let detectedLoopSize = 0;
64
+
65
+ // Track the last capture time to pace captures similar to image2texture
66
+ let lastCaptureTime = 0;
67
+ let captureInterval = 350; // Start with 350ms between captures
68
+
69
+ // Track total frames estimate
70
+ let totalFramesEstimate = 120; // Initial estimate
71
+
72
+ console.log('Starting CSS3D pre-rendering analysis...');
73
+
74
+ // Get animation settings from passed settings object instead of DOM
75
+ let isLongExposureMode = false;
76
+ let playbackSpeed = 1.0;
77
+
78
+ if (settings) {
79
+ // Use settings parameters instead of DOM elements
80
+ isLongExposureMode = settings.isLongExposureMode;
81
+ playbackSpeed = settings.playbackSpeed || 1.0;
82
+ } else {
83
+ // Fallback to DOM access if settings not provided (for backward compatibility)
84
+ const animationTypeSelect = document.getElementById('html-animation-type');
85
+ isLongExposureMode = animationTypeSelect && animationTypeSelect.value === 'longExposure';
86
+ }
87
+
88
+ // Set flag if we're capturing for long exposure
89
+ if (isLongExposureMode) {
90
+ setCapturingForLongExposure(true);
91
+
92
+ // Temporarily disable borders during capture
93
+ const originalBorderSetting = window.showPreviewBorders;
94
+ window.showPreviewBorders = false;
95
+ console.log('Borders temporarily disabled for long exposure capture');
96
+
97
+ // Store original setting to restore later
98
+ window._originalBorderSetting = originalBorderSetting;
99
+ }
100
+
101
+ // Function to update progress bar
102
+ const updateProgress = (percent) => {
103
+ if (progressBar) {
104
+ // Ensure progress never exceeds maxProgressBeforeFinalAnimation unless in final animation
105
+ if (!finalProgressAnimation && percent > maxProgressBeforeFinalAnimation) {
106
+ percent = maxProgressBeforeFinalAnimation;
107
+ }
108
+ progressBar.style.width = `${percent}%`;
109
+ }
110
+ };
111
+
112
+ // Function to create the long exposure texture and apply it
113
+ const createAndApplyCss3dLongExposure = () => {
114
+ if (domSnapshotFrames.length > 0) {
115
+ // Use playbackSpeed from settings instead of DOM
116
+ const longExposureTexture = createLongExposureTexture(domSnapshotFrames, playbackSpeed);
117
+
118
+ // Update the iframe with the long exposure texture
119
+ if (previewPlane) {
120
+ updateMeshTexture(longExposureTexture, previewPlane);
121
+ }
122
+
123
+ // Show a message about the long exposure
124
+ showStatus(`CSS3D Long exposure created from ${domSnapshotFrames.length} frames`, 'success');
125
+
126
+ // Pause animation since we just want to display the static image
127
+ setIsPreviewAnimationPaused(true);
128
+ }
129
+ };
130
+
131
+ // Function to start final progress animation
132
+ const startFinalProgressAnimation = () => {
133
+ if (finalProgressAnimation) return; // Already animating
134
+
135
+ setFinalProgressAnimation(true);
136
+ setFinalProgressStartTime(Date.now());
137
+
138
+ // Start the animation loop
139
+ animateFinalProgress();
140
+ };
141
+
142
+ // Function to animate progress to 100%
143
+ const animateFinalProgress = () => {
144
+ const now = Date.now();
145
+ const elapsed = now - finalProgressStartTime;
146
+
147
+ if (elapsed >= finalProgressDuration) {
148
+ // Animation complete, set to 100%
149
+ updateProgress(100);
150
+
151
+ // Log animation analysis report
152
+ logAnimationAnalysisReport('CSS3D', {
153
+ frameCount: domSnapshotFrames.length,
154
+ duration: animationDuration,
155
+ isFinite: isAnimationFinite,
156
+ loopDetected,
157
+ endDetected,
158
+ analysisTime: now - preRenderStartTime,
159
+ metrics: {
160
+ ...analysisMetrics,
161
+ loopSize: detectedLoopSize,
162
+ domSnapshotCount: domSnapshotFrames.length
163
+ }
164
+ });
165
+
166
+ // Store the DOM snapshot frames in the preRenderedFrames array for compatibility
167
+ setPreRenderedFrames(domSnapshotFrames);
168
+
169
+ // Hide loading overlay with fade out
170
+ const loadingOverlay = document.getElementById('pre-rendering-overlay');
171
+ if (loadingOverlay) {
172
+ loadingOverlay.style.transition = 'opacity 0.5s ease';
173
+ loadingOverlay.style.opacity = '0';
174
+
175
+ // Remove after fade out
176
+ setTimeout(() => {
177
+ if (loadingOverlay.parentNode) {
178
+ loadingOverlay.parentNode.removeChild(loadingOverlay);
179
+ }
180
+
181
+ // CRITICAL: Initialize playback timing for ALL animation types
182
+ initializePlaybackTiming();
183
+
184
+ // Now create the info panel after pre-rendering is complete
185
+ const canvasContainer = document.querySelector('#html-preview-content');
186
+ if (canvasContainer) {
187
+ const modal = document.getElementById('html-editor-modal');
188
+ const currentMeshId = parseInt(modal.dataset.meshId);
189
+ createMeshInfoPanel(canvasContainer, currentMeshId);
190
+ }
191
+
192
+ // Handle different animation types
193
+ if (isLongExposureMode && preRenderedFrames.length > 0) {
194
+ // For long exposure, create the static image
195
+ const longExposureTexture = createLongExposureTexture(preRenderedFrames, playbackSpeed);
196
+
197
+ if (previewPlane) {
198
+ updateMeshTexture(longExposureTexture, previewPlane);
199
+ }
200
+
201
+ showStatus(`Long exposure created from ${preRenderedFrames.length} frames`, 'success');
202
+ setIsPreviewAnimationPaused(true);
203
+ } else {
204
+ // For all other types, start playback
205
+ setIsPreviewAnimationPaused(false);
206
+
207
+ const typeDescription = isAnimationFinite ?
208
+ `finite (${(animationDuration/1000).toFixed(1)}s)` :
209
+ 'static/infinite';
210
+
211
+ showStatus(`Animation playback starting - ${typeDescription}, ${preRenderedFrames.length} frames at ${playbackSpeed}x speed`, 'success');
212
+ }
213
+
214
+ // Execute the callback if provided
215
+ if (typeof callback === 'function') {
216
+ callback();
217
+ }
218
+ }, 500);
219
+
220
+ // Don't continue the animation
221
+ return;
222
+ } else {
223
+ // If no loading overlay found, still execute the callback
224
+ if (typeof callback === 'function') {
225
+ console.log('Executing CSS3D pre-rendering callback (no overlay)');
226
+ callback();
227
+ }
228
+ }
229
+ } else {
230
+ // Calculate progress based on easing function
231
+ const progress = easeOutCubic(elapsed / finalProgressDuration);
232
+ const currentProgress = maxProgressBeforeFinalAnimation + (100 - maxProgressBeforeFinalAnimation) * progress;
233
+ updateProgress(currentProgress);
234
+
235
+ // Update loading text
236
+ const progressText = document.getElementById('loading-progress-text');
237
+ if (progressText) {
238
+ if (isLongExposureMode) {
239
+ progressText.textContent = 'Creating CSS3D long exposure...';
240
+ } else {
241
+ progressText.textContent = 'Finalizing CSS3D animation...';
242
+ }
243
+ }
244
+
245
+ // Continue animation
246
+ requestAnimationFrame(animateFinalProgress);
247
+ }
248
+ };
249
+
250
+ // Easing function for smooth animation
251
+ const easeOutCubic = (x) => {
252
+ return 1 - Math.pow(1 - x, 3);
253
+ };
254
+
255
+ // Create a DOM snapshot from the iframe
256
+ const createDomSnapshot = (iframe) => {
257
+ try {
258
+ if (!iframe || !iframe.contentDocument || !iframe.contentDocument.documentElement) {
259
+ return null;
260
+ }
261
+
262
+ // Clone the DOM for snapshot
263
+ const domClone = iframe.contentDocument.documentElement.cloneNode(true);
264
+
265
+ // Calculate a hash of the DOM to detect changes
266
+ const domHash = calculateDomHash(domClone);
267
+
268
+ return {
269
+ domSnapshot: domClone,
270
+ hash: domHash
271
+ };
272
+ } catch (error) {
273
+ console.error('Error creating DOM snapshot:', error);
274
+ return null;
275
+ }
276
+ };
277
+
278
+ // Calculate a hash of the DOM to detect changes
279
+ const calculateDomHash = (domElement) => {
280
+ try {
281
+ // Extract relevant attributes for comparison
282
+ const attributes = [];
283
+
284
+ // Track animation state separately
285
+ let hasAnimations = false;
286
+ let hasTransitions = false;
287
+
288
+ // Process element and its children recursively
289
+ const processElement = (element) => {
290
+ // Skip script elements
291
+ if (element.tagName === 'SCRIPT') return;
292
+
293
+ // Get element attributes
294
+ const tagName = element.tagName || '';
295
+ const className = element.className || '';
296
+ const id = element.id || '';
297
+ const style = element.style ? element.style.cssText : '';
298
+
299
+ // Check for animations and transitions in style
300
+ if (style.includes('animation') || style.includes('keyframes')) {
301
+ hasAnimations = true;
302
+ }
303
+ if (style.includes('transition')) {
304
+ hasTransitions = true;
305
+ }
306
+
307
+ // Extract more detailed style information
308
+ const transform = style.match(/transform:[^;]+/) || '';
309
+ const opacity = style.match(/opacity:[^;]+/) || '';
310
+ const position = style.match(/((left|top|right|bottom):[^;]+)/) || '';
311
+ const animation = style.match(/animation:[^;]+/) || '';
312
+ const transition = style.match(/transition:[^;]+/) || '';
313
+ const backgroundColor = style.match(/background-color:[^;]+/) || '';
314
+ const color = style.match(/color:[^;]+/) || '';
315
+
316
+ // Add element info to attributes array with more details
317
+ attributes.push(`${tagName}#${id}.${className}[${transform}][${opacity}][${position}][${animation}][${transition}][${backgroundColor}][${color}]`);
318
+
319
+ // Process child elements
320
+ if (element.children) {
321
+ for (let i = 0; i < element.children.length; i++) {
322
+ processElement(element.children[i]);
323
+ }
324
+ }
325
+ };
326
+
327
+ // Start processing from the root element
328
+ processElement(domElement);
329
+
330
+ // Join all attributes and hash them
331
+ const attributesString = attributes.join('|');
332
+
333
+ // Add animation state to the hash
334
+ const stateInfo = `animations:${hasAnimations}|transitions:${hasTransitions}`;
335
+ const fullString = attributesString + '|' + stateInfo;
336
+
337
+ // Create a simple hash
338
+ let hash = 0;
339
+ for (let i = 0; i < fullString.length; i++) {
340
+ const char = fullString.charCodeAt(i);
341
+ hash = ((hash << 5) - hash) + char;
342
+ hash = hash & hash; // Convert to 32bit integer
343
+ }
344
+
345
+ return hash.toString();
346
+ } catch (e) {
347
+ console.error('Error calculating DOM hash:', e);
348
+ return Math.random().toString(); // Fallback to random hash
349
+ }
350
+ };
351
+
352
+ // Calculate the difference between two DOM hashes
353
+ const calculateDomHashDifference = (hash1, hash2) => {
354
+ if (!hash1 || !hash2) return 1;
355
+
356
+ try {
357
+ // Convert hashes to numbers
358
+ const num1 = parseInt(hash1);
359
+ const num2 = parseInt(hash2);
360
+
361
+ // Calculate normalized difference (0-1)
362
+ // Use a more conservative normalization factor
363
+ const diff = Math.abs(num1 - num2) / (Math.pow(2, 30) - 1);
364
+ return Math.min(1, diff);
365
+ } catch (e) {
366
+ console.error('Error calculating hash difference:', e);
367
+ return 1;
368
+ }
369
+ };
370
+
371
+ // Detect CSS3D animation loops
372
+ const detectCSS3DAnimationLoop = (frames, currentHash) => {
373
+ // Need at least 30 frames to detect a loop (increased from 20)
374
+ if (frames.length < 30) {
375
+ return { loopDetected: false, loopSize: 0 };
376
+ }
377
+
378
+ // Use a much more conservative threshold for CSS3D
379
+ const minLoopSize = 6; // Increased minimum loop size (was 4)
380
+ const maxLoopSize = Math.floor(frames.length / 3); // Reduced from frames.length/2
381
+ const loopThreshold = 0.2; // Much more conservative threshold (was 0.1)
382
+
383
+ // Try different loop sizes
384
+ for (let loopSize = minLoopSize; loopSize <= maxLoopSize; loopSize++) {
385
+ let isLoop = true;
386
+ let matchScore = 0;
387
+
388
+ // Compare the last loopSize frames with the previous loopSize frames
389
+ for (let i = 0; i < loopSize; i++) {
390
+ const currentIndex = frames.length - 1 - i;
391
+ const previousIndex = currentIndex - loopSize;
392
+
393
+ if (previousIndex < 0) {
394
+ isLoop = false;
395
+ break;
396
+ }
397
+
398
+ const currentFrameHash = i === 0 ? currentHash : frames[currentIndex].hash;
399
+ const previousFrameHash = frames[previousIndex].hash;
400
+
401
+ // Calculate difference
402
+ const diff = calculateDomHashDifference(currentFrameHash, previousFrameHash);
403
+
404
+ // If hashes are different by more than the threshold, it's not a loop
405
+ if (diff > loopThreshold) {
406
+ isLoop = false;
407
+ break;
408
+ }
409
+
410
+ // Track how close the match is
411
+ matchScore += (1 - diff);
412
+ }
413
+
414
+ // Only consider it a loop if we have a good match score
415
+ const avgMatchScore = matchScore / loopSize;
416
+ if (isLoop && avgMatchScore > 0.7) { // Require at least 70% match confidence
417
+ console.log(`Detected CSS3D animation loop of ${loopSize} frames with match score ${avgMatchScore.toFixed(2)}`);
418
+ return { loopDetected: true, loopSize, matchScore: avgMatchScore };
419
+ }
420
+ }
421
+
422
+ return { loopDetected: false, loopSize: 0, matchScore: 0 };
423
+ };
424
+
425
+ // Function to analyze CSS3D animation state
426
+ const analyzeCSS3DAnimation = (iframe, domSnapshotFrames, currentHash) => {
427
+ try {
428
+ if (!iframe || !iframe.contentWindow || !iframe.contentWindow.__css3dAnimationDetection) {
429
+ return {
430
+ isAnimating: false,
431
+ metrics: {}
432
+ };
433
+ }
434
+
435
+ const detection = iframe.contentWindow.__css3dAnimationDetection;
436
+ const now = Date.now();
437
+
438
+ // Check if there are active animations
439
+ const hasActiveTimeouts = detection.activeTimeouts > 0;
440
+ const hasActiveIntervals = detection.activeIntervals > 0;
441
+ const hasActiveRAF = detection.rAF > 0 && detection.animationFrameIds.size > 0;
442
+ const hasCssAnimations = detection.cssAnimations && detection.cssAnimations.size > 0;
443
+ const hasCssTransitions = detection.cssTransitions && detection.cssTransitions.size > 0;
444
+
445
+ // Check for recent DOM changes
446
+ const timeSinceLastDomChange = now - (detection.lastDomChange || 0);
447
+ const hasRecentDomChanges = timeSinceLastDomChange < 500; // Consider DOM changes in last 500ms as active
448
+
449
+ // Determine if animation is active
450
+ const isAnimating = hasActiveTimeouts || hasActiveIntervals || hasActiveRAF ||
451
+ hasCssAnimations || hasCssTransitions || hasRecentDomChanges;
452
+
453
+ // Check for loop patterns in DOM snapshots
454
+ let loopDetected = false;
455
+ let loopSize = 0;
456
+ let matchScore = 0;
457
+
458
+ // Only try to detect loops if we have enough frames and animation seems to be happening
459
+ if (domSnapshotFrames.length >= 30 && (isAnimating || detection.domChanges > 10)) {
460
+ const result = detectCSS3DAnimationLoop(domSnapshotFrames, currentHash);
461
+ loopDetected = result.loopDetected;
462
+ loopSize = result.loopSize;
463
+ matchScore = result.matchScore;
464
+ }
465
+
466
+ return {
467
+ isAnimating,
468
+ loopDetected,
469
+ loopSize,
470
+ matchScore,
471
+ metrics: {
472
+ activeTimeouts: detection.activeTimeouts,
473
+ activeIntervals: detection.activeIntervals,
474
+ rAF: detection.rAF,
475
+ cssAnimations: detection.cssAnimations ? detection.cssAnimations.size : 0,
476
+ cssTransitions: detection.cssTransitions ? detection.cssTransitions.size : 0,
477
+ domChanges: detection.domChanges,
478
+ timeSinceLastDomChange,
479
+ matchScore
480
+ }
481
+ };
482
+ } catch (e) {
483
+ console.error('Error analyzing CSS3D animation:', e);
484
+ return {
485
+ isAnimating: false,
486
+ metrics: {}
487
+ };
488
+ }
489
+ };
490
+
491
+ // Function to capture DOM snapshots until animation completes or times out
492
+ const captureDomSnapshots = async () => {
493
+ if (!isPreviewActive || !preRenderingInProgress) {
494
+ setPreRenderingInProgress(false);
495
+ startFinalProgressAnimation();
496
+ return;
497
+ }
498
+
499
+ const now = Date.now();
500
+
501
+ // Check if enough time has passed since last capture
502
+ // This ensures we capture at a similar rate to image2texture
503
+ const timeSinceLastCapture = now - lastCaptureTime;
504
+ if (timeSinceLastCapture < captureInterval) {
505
+ // Schedule next check
506
+ requestAnimationFrame(captureDomSnapshots);
507
+ return;
508
+ }
509
+
510
+ // Update last capture time
511
+ lastCaptureTime = now;
512
+
513
+ // Update progress based on more accurate metrics
514
+ if (now - lastProgressUpdate > progressUpdateInterval) {
515
+ lastProgressUpdate = now;
516
+
517
+ // Calculate elapsed time percentage
518
+ const elapsedTime = now - preRenderStartTime;
519
+ const timeProgress = Math.min(90, (elapsedTime / preRenderMaxDuration) * 100);
520
+
521
+ // Calculate frame-based progress
522
+ let frameProgress = Math.min(90, (domSnapshotFrames.length / totalFramesEstimate) * 100);
523
+
524
+ // Use a weighted combination of time and frame progress
525
+ // Cap at maxProgressBeforeFinalAnimation to leave room for final animation
526
+ const combinedProgress = Math.min(
527
+ maxProgressBeforeFinalAnimation,
528
+ (timeProgress * 0.3) + (frameProgress * 0.7)
529
+ );
530
+ updateProgress(combinedProgress);
531
+
532
+ // Update the loading text to show more information
533
+ const progressText = document.getElementById('loading-progress-text');
534
+ if (progressText) {
535
+ progressText.textContent = `Analyzing CSS3D animation... ${domSnapshotFrames.length} snapshots captured`;
536
+ }
537
+ }
538
+
539
+ // Inject animation detection script if not already done
540
+ if (domSnapshotFrames.length === 0) {
541
+ injectUnifiedAnimationDetectionScript(iframe, 'css3d');
542
+ }
543
+
544
+ // Create a DOM snapshot
545
+ const snapshot = createDomSnapshot(iframe);
546
+
547
+ if (snapshot) {
548
+ // Add snapshot to frames
549
+ domSnapshotFrames.push({
550
+ ...snapshot,
551
+ timestamp: now
552
+ });
553
+
554
+ // Only start analyzing after we have enough frames
555
+ // Use same threshold as image2texture (20 frames)
556
+ if (domSnapshotFrames.length >= 20) {
557
+ // Analyze the CSS3D animation
558
+ const analysis = analyzeCSS3DAnimation(
559
+ iframe,
560
+ domSnapshotFrames.slice(0, -1),
561
+ snapshot.hash
562
+ );
563
+
564
+ // Store analysis metrics
565
+ analysisMetrics = analysis.metrics;
566
+
567
+ // Check if we've detected a loop with high confidence
568
+ if (analysis.loopDetected && analysis.matchScore > 0.7) {
569
+ console.log('CSS3D animation loop detected after ' + domSnapshotFrames.length + ' snapshots with match score ' + analysis.matchScore.toFixed(2));
570
+ setPreRenderingInProgress(false);
571
+ setIsAnimationFinite(true);
572
+ setAnimationDuration(domSnapshotFrames[domSnapshotFrames.length - 1].timestamp - domSnapshotFrames[0].timestamp);
573
+
574
+ // Update analysis metrics
575
+ loopDetected = true;
576
+ detectedLoopSize = analysis.loopSize;
577
+
578
+ // Show success message
579
+ if (isLongExposureMode) {
580
+ showStatus(`CSS3D animation loop detected, creating long exposure from ${domSnapshotFrames.length} frames`, 'info');
581
+ } else {
582
+ showStatus(`CSS3D animation loop detected (${(animationDuration/1000).toFixed(1)}s), ${domSnapshotFrames.length} snapshots captured`, 'success');
583
+ }
584
+
585
+ // Start final progress animation
586
+ startFinalProgressAnimation();
587
+ return;
588
+ }
589
+
590
+ // Check if animation has ended (no changes for a while)
591
+ if (domSnapshotFrames.length > 20 && !analysis.isAnimating) {
592
+ // Check if there have been no significant changes in the last few frames
593
+ let noChanges = true;
594
+ const recentFrames = domSnapshotFrames.slice(-5);
595
+
596
+ for (let i = 1; i < recentFrames.length; i++) {
597
+ const diff = calculateDomHashDifference(recentFrames[i].hash, recentFrames[i-1].hash);
598
+ if (diff > 0.01) { // Small threshold for changes
599
+ noChanges = false;
600
+ break;
601
+ }
602
+ }
603
+
604
+ if (noChanges) {
605
+ console.log('CSS3D animation end detected after ' + domSnapshotFrames.length + ' snapshots');
606
+ setPreRenderingInProgress(false);
607
+ setIsAnimationFinite(true);
608
+ setAnimationDuration(domSnapshotFrames[domSnapshotFrames.length - 1].timestamp - domSnapshotFrames[0].timestamp);
609
+
610
+ // Update analysis metrics
611
+ endDetected = true;
612
+
613
+ // Show success message
614
+ showStatus(`CSS3D animation end detected (${(animationDuration/1000).toFixed(1)}s), ${domSnapshotFrames.length} snapshots captured`, 'success');
615
+
616
+ // Start final progress animation
617
+ startFinalProgressAnimation();
618
+ return;
619
+ }
620
+ }
621
+ }
622
+ }
623
+
624
+ // Check if we've exceeded the maximum pre-rendering time
625
+ if (now - preRenderStartTime > preRenderMaxDuration) {
626
+ console.log('CSS3D analysis time limit reached after ' + preRenderMaxDuration + 'ms');
627
+ setPreRenderingInProgress(false);
628
+
629
+ const analysis = analyzeCSS3DAnimation(iframe, domSnapshotFrames, null);
630
+
631
+ // Store final analysis metrics
632
+ analysisMetrics = analysis.metrics;
633
+
634
+ if (analysis.loopDetected) {
635
+ setIsAnimationFinite(true);
636
+ setAnimationDuration(domSnapshotFrames[domSnapshotFrames.length - 1].timestamp - domSnapshotFrames[0].timestamp);
637
+ console.log(`CSS3D animation loop detected, duration: ${animationDuration}ms, ${domSnapshotFrames.length} snapshots captured`);
638
+ showStatus(`CSS3D animation loop detected (${(animationDuration/1000).toFixed(1)}s), ${domSnapshotFrames.length} snapshots captured`, 'success');
639
+
640
+ // Update analysis metrics
641
+ loopDetected = true;
642
+ detectedLoopSize = analysis.loopSize;
643
+ } else if (!analysis.isAnimating) {
644
+ setIsAnimationFinite(true);
645
+ setAnimationDuration(domSnapshotFrames[domSnapshotFrames.length - 1].timestamp - domSnapshotFrames[0].timestamp);
646
+ console.log(`CSS3D animation appears to have ended, duration: ${animationDuration}ms, ${domSnapshotFrames.length} snapshots captured`);
647
+ showStatus(`CSS3D animation end detected (${(animationDuration/1000).toFixed(1)}s), ${domSnapshotFrames.length} snapshots captured`, 'success');
648
+
649
+ // Update analysis metrics
650
+ endDetected = true;
651
+ } else {
652
+ console.log(`No CSS3D animation end detected, ${domSnapshotFrames.length} snapshots captured`);
653
+ showStatus(`CSS3D animation appears infinite, ${domSnapshotFrames.length} snapshots captured for playback`, 'info');
654
+ }
655
+
656
+ // Start final progress animation
657
+ startFinalProgressAnimation();
658
+ return;
659
+ }
660
+
661
+ // Continue capturing snapshots with requestAnimationFrame
662
+ // The timing is controlled by the captureInterval check at the beginning
663
+ requestAnimationFrame(captureDomSnapshots);
664
+ };
665
+
666
+ // Start capturing DOM snapshots
667
+ captureDomSnapshots();
668
+
669
+ // Store callback to be called after final animation completes
670
+ window._preRenderCallback = callback;
671
+
672
+ // Store the DOM snapshot frames in the preRenderedFrames array for compatibility
673
+ setPreRenderedFrames(domSnapshotFrames);
674
+ }
675
+
676
+ /**
677
+ * Initialize playback timing - called when preview starts playing
678
+ * This should be called regardless of animation type (finite/infinite)
679
+ */
680
+ function initializePlaybackTiming() {
681
+ const now = Date.now();
682
+ setAnimationPlaybackStartTime(now);
683
+
684
+ // Calculate the capture start time from the first frame if available
685
+ if (preRenderedFrames.length > 0) {
686
+ setAnimationCaptureStartTime(preRenderedFrames[0].timestamp);
687
+ } else {
688
+ setAnimationCaptureStartTime(now);
689
+ }
690
+
691
+ console.log('Playback timing initialized:', {
692
+ playbackStart: animationPlaybackStartTime,
693
+ captureStart: animationCaptureStartTime,
694
+ framesAvailable: preRenderedFrames.length
695
+ });
696
+ }