@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.
- package/bin/cli.js +69 -0
- package/package.json +13 -7
- package/src/asset_debugger/axis-indicator/axis-indicator.css +6 -0
- package/src/asset_debugger/axis-indicator/axis-indicator.html +20 -0
- package/src/asset_debugger/axis-indicator/axis-indicator.js +822 -0
- package/src/asset_debugger/debugger-scene/debugger-scene.css +142 -0
- package/src/asset_debugger/debugger-scene/debugger-scene.html +80 -0
- package/src/asset_debugger/debugger-scene/debugger-scene.js +791 -0
- package/src/asset_debugger/header/header.css +73 -0
- package/src/asset_debugger/header/header.html +24 -0
- package/src/asset_debugger/header/header.js +224 -0
- package/src/asset_debugger/index.html +76 -0
- package/src/asset_debugger/landing-page/landing-page.css +396 -0
- package/src/asset_debugger/landing-page/landing-page.html +81 -0
- package/src/asset_debugger/landing-page/landing-page.js +611 -0
- package/src/asset_debugger/loading-splash/loading-splash.css +195 -0
- package/src/asset_debugger/loading-splash/loading-splash.html +22 -0
- package/src/asset_debugger/loading-splash/loading-splash.js +59 -0
- package/src/asset_debugger/loading-splash/preview-loading-splash.js +66 -0
- package/src/asset_debugger/main.css +14 -0
- package/src/asset_debugger/modals/examples-modal/examples-modal.css +41 -0
- package/src/asset_debugger/modals/examples-modal/examples-modal.html +18 -0
- package/src/asset_debugger/modals/examples-modal/examples-modal.js +111 -0
- package/src/asset_debugger/modals/examples-modal/examples.js +125 -0
- package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.css +452 -0
- package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.html +87 -0
- package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.js +675 -0
- package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.css +219 -0
- package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.html +20 -0
- package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.js +548 -0
- package/src/asset_debugger/modals/settings-modal/settings-modal.css +103 -0
- package/src/asset_debugger/modals/settings-modal/settings-modal.html +158 -0
- package/src/asset_debugger/modals/settings-modal/settings-modal.js +475 -0
- package/src/asset_debugger/panels/asset-panel/asset-panel.css +263 -0
- package/src/asset_debugger/panels/asset-panel/asset-panel.html +123 -0
- package/src/asset_debugger/panels/asset-panel/asset-panel.js +136 -0
- package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.css +94 -0
- package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.js +312 -0
- package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.css +129 -0
- package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.js +486 -0
- package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.css +545 -0
- package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.js +538 -0
- package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.css +70 -0
- package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.js +586 -0
- package/src/asset_debugger/panels/world-panel/world-panel.css +364 -0
- package/src/asset_debugger/panels/world-panel/world-panel.html +173 -0
- package/src/asset_debugger/panels/world-panel/world-panel.js +1891 -0
- package/src/asset_debugger/router.js +190 -0
- package/src/asset_debugger/util/animation/playback/animation-playback-controller.js +150 -0
- package/src/asset_debugger/util/animation/playback/animation-preview-controller.js +316 -0
- package/src/asset_debugger/util/animation/playback/css3d-bounce-controller.js +400 -0
- package/src/asset_debugger/util/animation/playback/css3d-reversal-controller.js +821 -0
- package/src/asset_debugger/util/animation/render/css3d-prerender-controller.js +696 -0
- package/src/asset_debugger/util/animation/render/debug-texture-factory.js +0 -0
- package/src/asset_debugger/util/animation/render/iframe2texture-render-controller.js +199 -0
- package/src/asset_debugger/util/animation/render/image2texture-prerender-controller.js +461 -0
- package/src/asset_debugger/util/animation/render/pbr-material-factory.js +82 -0
- package/src/asset_debugger/util/common.css +280 -0
- package/src/asset_debugger/util/data/animation-classifier.js +323 -0
- package/src/asset_debugger/util/data/duplicate-handler.js +20 -0
- package/src/asset_debugger/util/data/glb-buffer-manager.js +407 -0
- package/src/asset_debugger/util/data/glb-classifier.js +290 -0
- package/src/asset_debugger/util/data/html-formatter.js +76 -0
- package/src/asset_debugger/util/data/html-linter.js +276 -0
- package/src/asset_debugger/util/data/localstorage-manager.js +265 -0
- package/src/asset_debugger/util/data/mesh-html-manager.js +295 -0
- package/src/asset_debugger/util/data/string-serder.js +303 -0
- package/src/asset_debugger/util/data/texture-classifier.js +663 -0
- package/src/asset_debugger/util/data/upload/background-file-handler.js +292 -0
- package/src/asset_debugger/util/data/upload/dropzone-preview-controller.js +396 -0
- package/src/asset_debugger/util/data/upload/file-upload-manager.js +495 -0
- package/src/asset_debugger/util/data/upload/glb-file-handler.js +36 -0
- package/src/asset_debugger/util/data/upload/glb-preview-controller.js +317 -0
- package/src/asset_debugger/util/data/upload/lighting-file-handler.js +194 -0
- package/src/asset_debugger/util/data/upload/model-file-manager.js +104 -0
- package/src/asset_debugger/util/data/upload/texture-file-handler.js +166 -0
- package/src/asset_debugger/util/data/upload/zip-handler.js +686 -0
- package/src/asset_debugger/util/loaders/html2canvas-loader.js +107 -0
- package/src/asset_debugger/util/rig/bone-kinematics.js +403 -0
- package/src/asset_debugger/util/rig/rig-constraint-manager.js +618 -0
- package/src/asset_debugger/util/rig/rig-controller.js +612 -0
- package/src/asset_debugger/util/rig/rig-factory.js +628 -0
- package/src/asset_debugger/util/rig/rig-handle-factory.js +46 -0
- package/src/asset_debugger/util/rig/rig-label-factory.js +441 -0
- package/src/asset_debugger/util/rig/rig-mouse-handler.js +377 -0
- package/src/asset_debugger/util/rig/rig-state-manager.js +175 -0
- package/src/asset_debugger/util/rig/rig-tooltip-manager.js +267 -0
- package/src/asset_debugger/util/rig/rig-ui-factory.js +700 -0
- package/src/asset_debugger/util/scene/background-manager.js +284 -0
- package/src/asset_debugger/util/scene/camera-controller.js +243 -0
- package/src/asset_debugger/util/scene/css3d-debug-controller.js +406 -0
- package/src/asset_debugger/util/scene/css3d-frame-factory.js +113 -0
- package/src/asset_debugger/util/scene/css3d-scene-manager.js +529 -0
- package/src/asset_debugger/util/scene/glb-controller.js +208 -0
- package/src/asset_debugger/util/scene/lighting-manager.js +690 -0
- package/src/asset_debugger/util/scene/threejs-model-manager.js +437 -0
- package/src/asset_debugger/util/scene/threejs-preview-manager.js +207 -0
- package/src/asset_debugger/util/scene/threejs-preview-setup.js +478 -0
- package/src/asset_debugger/util/scene/threejs-scene-controller.js +286 -0
- package/src/asset_debugger/util/scene/ui-manager.js +107 -0
- package/src/asset_debugger/util/state/animation-state.js +128 -0
- package/src/asset_debugger/util/state/css3d-state.js +83 -0
- package/src/asset_debugger/util/state/glb-preview-state.js +31 -0
- package/src/asset_debugger/util/state/log-util.js +197 -0
- package/src/asset_debugger/util/state/scene-state.js +452 -0
- package/src/asset_debugger/util/state/threejs-state.js +54 -0
- package/src/asset_debugger/util/workers/lighting-worker.js +61 -0
- package/src/asset_debugger/util/workers/model-worker.js +109 -0
- package/src/asset_debugger/util/workers/texture-worker.js +54 -0
- package/src/asset_debugger/util/workers/worker-manager.js +212 -0
- package/src/asset_debugger/widgets/mesh-info-widget.js +280 -0
- package/src/index.html +261 -0
- package/src/index.js +8 -0
- package/vite.config.js +66 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { showLoadingSplash, updateLoadingProgress, hideLoadingSplash } from './loading-splash/loading-splash';
|
|
2
|
+
|
|
3
|
+
class Router {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.routes = new Map();
|
|
6
|
+
this.appDiv = document.getElementById('app');
|
|
7
|
+
this.currentModuleCleanup = null;
|
|
8
|
+
this.init();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
addRoute(path, handler) {
|
|
12
|
+
this.routes.set(path, handler);
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
init() {
|
|
17
|
+
this.isHandlingRoute = false;
|
|
18
|
+
|
|
19
|
+
window.addEventListener('hashchange', this.handleRouteDebounced.bind(this));
|
|
20
|
+
window.addEventListener('load', this.handleRouteDebounced.bind(this));
|
|
21
|
+
|
|
22
|
+
if (!window.location.hash) {
|
|
23
|
+
this.navigate('/landing');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async handleRouteDebounced() {
|
|
28
|
+
if (this.isHandlingRoute) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.isHandlingRoute = true;
|
|
33
|
+
try {
|
|
34
|
+
await this.handleRoute();
|
|
35
|
+
} finally {
|
|
36
|
+
this.isHandlingRoute = false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async handleRoute() {
|
|
41
|
+
const hash = window.location.hash.slice(1) || '/landing';
|
|
42
|
+
const handler = this.routes.get(hash);
|
|
43
|
+
|
|
44
|
+
if (handler) {
|
|
45
|
+
await handler();
|
|
46
|
+
} else {
|
|
47
|
+
this.navigate('/landing');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
navigate(path) {
|
|
52
|
+
window.location.hash = path;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
navigateToPage(targetPage, params = {}) {
|
|
56
|
+
const navigationEvent = new CustomEvent('routerNavigation', {
|
|
57
|
+
detail: { targetPage, params, timestamp: Date.now() }
|
|
58
|
+
});
|
|
59
|
+
window.dispatchEvent(navigationEvent);
|
|
60
|
+
|
|
61
|
+
const routeMap = {
|
|
62
|
+
'debugger-scene': '/debugger-scene',
|
|
63
|
+
'landing': '/landing',
|
|
64
|
+
'tools': '/tools'
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const route = routeMap[targetPage];
|
|
68
|
+
if (route) {
|
|
69
|
+
this.navigate(route);
|
|
70
|
+
} else {
|
|
71
|
+
this.navigate('/landing');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async clearContent() {
|
|
76
|
+
if (this.currentModuleCleanup) {
|
|
77
|
+
try {
|
|
78
|
+
const result = this.currentModuleCleanup();
|
|
79
|
+
this.currentModuleCleanup = null;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
this.currentModuleCleanup = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.cleanupCSS3DElements();
|
|
86
|
+
this.appDiv.innerHTML = '';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
cleanupCSS3DElements() {
|
|
90
|
+
const css3dRenderers = document.querySelectorAll('div[style*="position: absolute"][style*="z-index: 1000"]');
|
|
91
|
+
css3dRenderers.forEach(renderer => {
|
|
92
|
+
if (renderer.parentNode && renderer !== this.appDiv) {
|
|
93
|
+
renderer.parentNode.removeChild(renderer);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const css3dIframes = document.querySelectorAll('iframe[style*="border: 2px solid #00ff88"]');
|
|
98
|
+
css3dIframes.forEach(iframe => {
|
|
99
|
+
if (iframe.parentNode) {
|
|
100
|
+
iframe.parentNode.removeChild(iframe);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const css3dElements = document.querySelectorAll('[style*="pointer-events: none"][style*="position: absolute"]');
|
|
105
|
+
css3dElements.forEach(element => {
|
|
106
|
+
if (element.parentNode && element !== this.appDiv && element.style.zIndex === '1000') {
|
|
107
|
+
element.parentNode.removeChild(element);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async loadContent(url) {
|
|
113
|
+
await this.clearContent();
|
|
114
|
+
await this.ensureHeaderLoaded();
|
|
115
|
+
|
|
116
|
+
const response = await fetch(url);
|
|
117
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
118
|
+
|
|
119
|
+
const html = await response.text();
|
|
120
|
+
const contentToInsert = this.extractContent(html, url);
|
|
121
|
+
this.appDiv.innerHTML = contentToInsert;
|
|
122
|
+
|
|
123
|
+
await new Promise(resolve => requestAnimationFrame(resolve));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
extractContent(html, url) {
|
|
127
|
+
if (!html.includes('<!DOCTYPE html>')) {
|
|
128
|
+
return html;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const parser = new DOMParser();
|
|
132
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
133
|
+
const body = doc.body.cloneNode(true);
|
|
134
|
+
|
|
135
|
+
const elementsToRemove = [
|
|
136
|
+
'#header-container',
|
|
137
|
+
'script',
|
|
138
|
+
'link[rel="stylesheet"]'
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
elementsToRemove.forEach(selector => {
|
|
142
|
+
const elements = body.querySelectorAll(selector);
|
|
143
|
+
elements.forEach(el => el.remove());
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return body.innerHTML;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async ensureHeaderLoaded() {
|
|
150
|
+
const headerContainer = document.getElementById('header-container');
|
|
151
|
+
if (headerContainer && headerContainer.innerHTML.trim() === '') {
|
|
152
|
+
try {
|
|
153
|
+
const { loadHeader } = await import('./header/header.js');
|
|
154
|
+
await loadHeader('header-container');
|
|
155
|
+
} catch (error) {
|
|
156
|
+
// Header loading failed
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async initializeModule(modulePath, initFunctionName, routeName) {
|
|
162
|
+
try {
|
|
163
|
+
const module = await import(/* @vite-ignore */ `${modulePath}?t=${Date.now()}`);
|
|
164
|
+
|
|
165
|
+
if (module[initFunctionName]) {
|
|
166
|
+
const cleanup = await module[initFunctionName]();
|
|
167
|
+
|
|
168
|
+
if (cleanup && typeof cleanup === 'function') {
|
|
169
|
+
this.currentModuleCleanup = cleanup;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
// Module initialization failed
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const router = new Router();
|
|
179
|
+
|
|
180
|
+
router
|
|
181
|
+
.addRoute('/landing', async () => {
|
|
182
|
+
await router.loadContent('./landing-page/landing-page.html');
|
|
183
|
+
await router.initializeModule('./landing-page/landing-page.js', 'initalizeLandingPage', 'landing page');
|
|
184
|
+
})
|
|
185
|
+
.addRoute('/debugger-scene', async () => {
|
|
186
|
+
await router.loadContent('./debugger-scene/debugger-scene.html');
|
|
187
|
+
await router.initializeModule('./debugger-scene/debugger-scene.js', 'setupDebuggerScene', 'debugger scene');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
window.appRouter = router;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { frameInterval } from "./animation-preview-controller";
|
|
2
|
+
import {
|
|
3
|
+
ANALYSIS_DURATION_MS,
|
|
4
|
+
animationDuration,
|
|
5
|
+
isAnimationFinite,
|
|
6
|
+
isPlaybackActive,
|
|
7
|
+
playbackStartTime,
|
|
8
|
+
preRenderedFrames,
|
|
9
|
+
setIsPlaybackActive,
|
|
10
|
+
setIsPreviewAnimationPaused,
|
|
11
|
+
setPlaybackStartTime
|
|
12
|
+
} from "../../state/animation-state";
|
|
13
|
+
import { setLastAnimationTime } from "../../state/css3d-state";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Start playback timing - called when preview should begin playing
|
|
17
|
+
*/
|
|
18
|
+
export function startPlayback() {
|
|
19
|
+
setPlaybackStartTime(Date.now());
|
|
20
|
+
setIsPlaybackActive(true);
|
|
21
|
+
console.log('Playback started at:', playbackStartTime);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Stop playback timing
|
|
26
|
+
*/
|
|
27
|
+
export function stopPlayback() {
|
|
28
|
+
setIsPlaybackActive(false);
|
|
29
|
+
console.log('Playback stopped');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get current frame based on elapsed playback time
|
|
34
|
+
*/
|
|
35
|
+
export function getCurrentFrameForPlayback(playbackSpeed = 1.0, animationType = 'play') {
|
|
36
|
+
if (!isPlaybackActive || preRenderedFrames.length === 0) {
|
|
37
|
+
return preRenderedFrames.length > 0 ? preRenderedFrames[preRenderedFrames.length - 1] : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
const playbackElapsed = now - playbackStartTime;
|
|
42
|
+
const adjustedElapsed = playbackElapsed * playbackSpeed;
|
|
43
|
+
|
|
44
|
+
// Use the analysis duration as our animation duration
|
|
45
|
+
// This represents the full time period we analyzed for animation
|
|
46
|
+
const naturalDuration = isAnimationFinite && animationDuration > 0 ?
|
|
47
|
+
animationDuration : ANALYSIS_DURATION_MS;
|
|
48
|
+
|
|
49
|
+
let normalizedTime;
|
|
50
|
+
|
|
51
|
+
switch (animationType) {
|
|
52
|
+
case 'play':
|
|
53
|
+
if (adjustedElapsed >= naturalDuration) {
|
|
54
|
+
setIsPreviewAnimationPaused(true);
|
|
55
|
+
return preRenderedFrames[preRenderedFrames.length - 1];
|
|
56
|
+
}
|
|
57
|
+
normalizedTime = adjustedElapsed / naturalDuration;
|
|
58
|
+
break;
|
|
59
|
+
|
|
60
|
+
case 'loop':
|
|
61
|
+
normalizedTime = (adjustedElapsed % naturalDuration) / naturalDuration;
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case 'bounce':
|
|
65
|
+
const cycle = Math.floor(adjustedElapsed / naturalDuration);
|
|
66
|
+
const position = (adjustedElapsed % naturalDuration) / naturalDuration;
|
|
67
|
+
normalizedTime = (cycle % 2 === 0) ? position : (1 - position);
|
|
68
|
+
break;
|
|
69
|
+
|
|
70
|
+
default:
|
|
71
|
+
normalizedTime = (adjustedElapsed % naturalDuration) / naturalDuration;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const frameIndex = Math.min(
|
|
76
|
+
Math.floor(normalizedTime * preRenderedFrames.length),
|
|
77
|
+
preRenderedFrames.length - 1
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return preRenderedFrames[frameIndex];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Update the mesh texture with the given texture
|
|
87
|
+
* @param {THREE.Texture} texture - The texture to apply to the mesh
|
|
88
|
+
* @param {THREE.Mesh} previewPlane - The mesh to update with the texture
|
|
89
|
+
*/
|
|
90
|
+
export function updateMeshTexture(texture, previewPlane) {
|
|
91
|
+
if (!texture || !previewPlane || !previewPlane.material) return;
|
|
92
|
+
|
|
93
|
+
let needsUpdate = false;
|
|
94
|
+
|
|
95
|
+
if (Array.isArray(previewPlane.material)) {
|
|
96
|
+
previewPlane.material.forEach(material => {
|
|
97
|
+
if (material.map !== texture) {
|
|
98
|
+
material.map = texture;
|
|
99
|
+
needsUpdate = true;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (needsUpdate) {
|
|
104
|
+
previewPlane.material.forEach(material => {
|
|
105
|
+
material.needsUpdate = true;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
if (previewPlane.material.map !== texture) {
|
|
110
|
+
previewPlane.material.map = texture;
|
|
111
|
+
previewPlane.material.needsUpdate = true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function runAnimationFrame(renderer, scene, camera, mesh, settings, frameState) {
|
|
117
|
+
// Extract settings
|
|
118
|
+
const { animationType, playbackSpeed, isPaused } = settings;
|
|
119
|
+
const { lastFrameTime, frameInterval } = frameState;
|
|
120
|
+
// Frame rate throttling
|
|
121
|
+
const now = performance.now();
|
|
122
|
+
const elapsed = now - lastFrameTime;
|
|
123
|
+
if (elapsed < frameInterval) {
|
|
124
|
+
return null; // Return null to indicate no frame update
|
|
125
|
+
}
|
|
126
|
+
const newFrameTime = now - (elapsed % frameInterval);
|
|
127
|
+
// Skip frame updates if animation is paused
|
|
128
|
+
if (isPaused) {
|
|
129
|
+
// Still render the scene with the current frame
|
|
130
|
+
if (renderer && scene && camera) {
|
|
131
|
+
renderer.render(scene, camera);
|
|
132
|
+
}
|
|
133
|
+
return newFrameTime;
|
|
134
|
+
}
|
|
135
|
+
// Get current frame
|
|
136
|
+
const currentFrame = getCurrentFrameForPlayback(playbackSpeed, animationType);
|
|
137
|
+
// Update texture
|
|
138
|
+
if (currentFrame && currentFrame.texture && mesh) {
|
|
139
|
+
updateMeshTexture(currentFrame.texture, mesh);
|
|
140
|
+
}
|
|
141
|
+
// Apply mesh animation (this would need to be imported if moved to separate module)
|
|
142
|
+
if (animationType !== 'play' && !isPaused && mesh) {
|
|
143
|
+
applyMeshAnimation(animationType, playbackSpeed, mesh);
|
|
144
|
+
}
|
|
145
|
+
// Render scene
|
|
146
|
+
if (renderer && scene && camera) {
|
|
147
|
+
renderer.render(scene, camera);
|
|
148
|
+
}
|
|
149
|
+
return newFrameTime;
|
|
150
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { CustomTextureSettings, showStatus } from '../../../modals/html-editor-modal/html-editor-modal';
|
|
3
|
+
import {
|
|
4
|
+
resetPreRenderState,
|
|
5
|
+
setIsPreviewActive,
|
|
6
|
+
setIsPreviewAnimationPaused,
|
|
7
|
+
resetPlaybackTimingState,
|
|
8
|
+
isPreviewAnimationPaused,
|
|
9
|
+
isPreviewActive
|
|
10
|
+
} from '../../state/animation-state';
|
|
11
|
+
import { sanitizeHtml } from '../../data/string-serder';
|
|
12
|
+
import {
|
|
13
|
+
animationPreviewCamera,
|
|
14
|
+
animationPreviewRenderer,
|
|
15
|
+
animationPreviewScene,
|
|
16
|
+
previewPlane,
|
|
17
|
+
setPreviewRenderTarget
|
|
18
|
+
} from '../../state/threejs-state';
|
|
19
|
+
import { runAnimationFrame } from '../../animation/playback/animation-playback-controller';
|
|
20
|
+
import { setupCSS3DScene } from '../../scene/css3d-scene-manager';
|
|
21
|
+
import { startImage2TexturePreRendering } from '../../animation/render/image2texture-prerender-controller';
|
|
22
|
+
import { startCss3dPreRendering } from '../../animation/render/css3d-prerender-controller';
|
|
23
|
+
import { logError, logPreviewError } from '../../state/log-util';
|
|
24
|
+
import { cleanupThreeJsPreview, initThreeJsPreview } from '../../scene/threejs-preview-manager';
|
|
25
|
+
|
|
26
|
+
let currentPreviewSettings = null;
|
|
27
|
+
const targetFrameRate = 60;
|
|
28
|
+
let lastAnimationFrameTime = 0;
|
|
29
|
+
export const frameInterval = 1000 / targetFrameRate;
|
|
30
|
+
export let previewAnimationId = null;
|
|
31
|
+
|
|
32
|
+
// Core HTML rendering logic (reusable)
|
|
33
|
+
function createHtmlRenderer(html, onLoad) {
|
|
34
|
+
const sanitizedHtml = sanitizeHtml(html);
|
|
35
|
+
|
|
36
|
+
const renderIframe = document.createElement('iframe');
|
|
37
|
+
renderIframe.id = 'html-render-iframe';
|
|
38
|
+
renderIframe.style.width = '960px';
|
|
39
|
+
renderIframe.style.height = '540px';
|
|
40
|
+
renderIframe.style.position = 'absolute';
|
|
41
|
+
renderIframe.style.left = '-9999px';
|
|
42
|
+
renderIframe.style.top = '0';
|
|
43
|
+
renderIframe.style.border = 'none';
|
|
44
|
+
renderIframe.style.backgroundColor = 'transparent';
|
|
45
|
+
document.body.appendChild(renderIframe);
|
|
46
|
+
|
|
47
|
+
renderIframe.onload = onLoad;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
renderIframe.srcdoc = sanitizedHtml;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
renderIframe.contentDocument.open();
|
|
53
|
+
renderIframe.contentDocument.write(sanitizedHtml);
|
|
54
|
+
renderIframe.contentDocument.close();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return renderIframe;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Preview UI creation (preview-specific)
|
|
61
|
+
function createPreviewUI(previewContent) {
|
|
62
|
+
previewContent.style.position = 'relative';
|
|
63
|
+
previewContent.style.minHeight = '400px';
|
|
64
|
+
previewContent.style.height = '100%';
|
|
65
|
+
|
|
66
|
+
const canvasContainer = document.createElement('div');
|
|
67
|
+
canvasContainer.style.width = '100%';
|
|
68
|
+
canvasContainer.style.height = '100%';
|
|
69
|
+
canvasContainer.style.position = 'absolute';
|
|
70
|
+
canvasContainer.style.top = '0';
|
|
71
|
+
canvasContainer.style.left = '0';
|
|
72
|
+
canvasContainer.style.right = '0';
|
|
73
|
+
canvasContainer.style.bottom = '0';
|
|
74
|
+
canvasContainer.style.overflow = 'hidden';
|
|
75
|
+
canvasContainer.style.display = 'block';
|
|
76
|
+
previewContent.appendChild(canvasContainer);
|
|
77
|
+
|
|
78
|
+
const errorLog = document.createElement('div');
|
|
79
|
+
errorLog.id = 'html-preview-error-log';
|
|
80
|
+
errorLog.className = 'preview-error-log';
|
|
81
|
+
errorLog.style.display = 'none';
|
|
82
|
+
previewContent.appendChild(errorLog);
|
|
83
|
+
|
|
84
|
+
const loadingOverlay = createLoadingOverlay();
|
|
85
|
+
canvasContainer.appendChild(loadingOverlay);
|
|
86
|
+
|
|
87
|
+
return { canvasContainer, errorLog, loadingOverlay };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function createLoadingOverlay() {
|
|
91
|
+
const loadingOverlay = document.createElement('div');
|
|
92
|
+
loadingOverlay.id = 'pre-rendering-overlay';
|
|
93
|
+
loadingOverlay.className = 'loading-splash';
|
|
94
|
+
loadingOverlay.style.position = 'absolute';
|
|
95
|
+
loadingOverlay.style.top = '0';
|
|
96
|
+
loadingOverlay.style.left = '0';
|
|
97
|
+
loadingOverlay.style.width = '100%';
|
|
98
|
+
loadingOverlay.style.height = '100%';
|
|
99
|
+
loadingOverlay.style.backgroundColor = '#000000';
|
|
100
|
+
loadingOverlay.style.zIndex = '1000';
|
|
101
|
+
loadingOverlay.style.border = 'none';
|
|
102
|
+
loadingOverlay.style.outline = 'none';
|
|
103
|
+
loadingOverlay.style.boxShadow = 'none';
|
|
104
|
+
|
|
105
|
+
const loadingContent = document.createElement('div');
|
|
106
|
+
loadingContent.className = 'loading-content';
|
|
107
|
+
loadingContent.style.display = 'flex';
|
|
108
|
+
loadingContent.style.flexDirection = 'column';
|
|
109
|
+
loadingContent.style.alignItems = 'center';
|
|
110
|
+
loadingContent.style.justifyContent = 'center';
|
|
111
|
+
loadingContent.style.height = '100%';
|
|
112
|
+
loadingContent.style.width = '100%';
|
|
113
|
+
loadingContent.style.backgroundColor = '#000000';
|
|
114
|
+
|
|
115
|
+
const loadingTitle = document.createElement('h2');
|
|
116
|
+
loadingTitle.className = 'loading-title';
|
|
117
|
+
loadingTitle.textContent = 'PRE-RENDERING';
|
|
118
|
+
loadingTitle.style.color = 'white';
|
|
119
|
+
loadingTitle.style.margin = '0 0 20px 0';
|
|
120
|
+
|
|
121
|
+
const spinnerContainer = document.createElement('div');
|
|
122
|
+
spinnerContainer.className = 'loading-spinner-container';
|
|
123
|
+
|
|
124
|
+
const atomicSpinner = document.createElement('div');
|
|
125
|
+
atomicSpinner.className = 'atomic-spinner';
|
|
126
|
+
|
|
127
|
+
const nucleus = document.createElement('div');
|
|
128
|
+
nucleus.className = 'nucleus';
|
|
129
|
+
atomicSpinner.appendChild(nucleus);
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < 3; i++) {
|
|
132
|
+
const orbit = document.createElement('div');
|
|
133
|
+
orbit.className = 'electron-orbit';
|
|
134
|
+
const electron = document.createElement('div');
|
|
135
|
+
electron.className = 'electron';
|
|
136
|
+
orbit.appendChild(electron);
|
|
137
|
+
atomicSpinner.appendChild(orbit);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
spinnerContainer.appendChild(atomicSpinner);
|
|
141
|
+
|
|
142
|
+
const progressText = document.createElement('div');
|
|
143
|
+
progressText.id = 'loading-progress-text';
|
|
144
|
+
progressText.className = 'loading-progress-text';
|
|
145
|
+
progressText.textContent = 'Pre-rendering animation...';
|
|
146
|
+
progressText.style.color = 'white';
|
|
147
|
+
progressText.style.marginTop = '20px';
|
|
148
|
+
|
|
149
|
+
const progressContainer = document.createElement('div');
|
|
150
|
+
progressContainer.style.width = '80%';
|
|
151
|
+
progressContainer.style.height = '4px';
|
|
152
|
+
progressContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.2)';
|
|
153
|
+
progressContainer.style.borderRadius = '2px';
|
|
154
|
+
progressContainer.style.overflow = 'hidden';
|
|
155
|
+
progressContainer.style.marginTop = '10px';
|
|
156
|
+
|
|
157
|
+
const progressBar = document.createElement('div');
|
|
158
|
+
progressBar.id = 'pre-rendering-progress';
|
|
159
|
+
progressBar.style.width = '0%';
|
|
160
|
+
progressBar.style.height = '100%';
|
|
161
|
+
progressBar.style.backgroundColor = '#3498db';
|
|
162
|
+
progressBar.style.transition = 'width 0.3s ease-out';
|
|
163
|
+
|
|
164
|
+
progressContainer.appendChild(progressBar);
|
|
165
|
+
loadingContent.appendChild(loadingTitle);
|
|
166
|
+
loadingContent.appendChild(spinnerContainer);
|
|
167
|
+
loadingContent.appendChild(progressText);
|
|
168
|
+
loadingContent.appendChild(progressContainer);
|
|
169
|
+
loadingOverlay.appendChild(loadingContent);
|
|
170
|
+
|
|
171
|
+
return loadingOverlay;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Main preview function (wrapper)
|
|
175
|
+
export function initalizePreview(settings, previewContent, setModalData) {
|
|
176
|
+
if (!previewContent) return;
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const { html, meshId: currentMeshId, previewMode, playbackSpeed, animationType, isLongExposureMode, showPreviewBorders } = settings;
|
|
180
|
+
|
|
181
|
+
currentPreviewSettings = settings;
|
|
182
|
+
window.showPreviewBorders = showPreviewBorders;
|
|
183
|
+
|
|
184
|
+
resetPreRenderState();
|
|
185
|
+
resetPlaybackTimingState();
|
|
186
|
+
|
|
187
|
+
if (setModalData) {
|
|
188
|
+
setModalData('previewMode', previewMode);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
cleanupThreeJsPreview();
|
|
192
|
+
previewContent.innerHTML = '';
|
|
193
|
+
setIsPreviewActive(true);
|
|
194
|
+
|
|
195
|
+
const statusMessage = isLongExposureMode
|
|
196
|
+
? 'Pre-rendering animation for long exposure capture...'
|
|
197
|
+
: 'Pre-rendering animation for smooth playback...';
|
|
198
|
+
settings.updateStatus(statusMessage, 'info');
|
|
199
|
+
|
|
200
|
+
const renderIframe = createHtmlRenderer(html, () => {
|
|
201
|
+
if (!isPreviewActive) return;
|
|
202
|
+
|
|
203
|
+
const { canvasContainer, errorLog, loadingOverlay } = createPreviewUI(previewContent);
|
|
204
|
+
const progressBar = loadingOverlay.querySelector('#pre-rendering-progress');
|
|
205
|
+
const progressText = loadingOverlay.querySelector('#loading-progress-text');
|
|
206
|
+
|
|
207
|
+
setPreviewRenderTarget(renderIframe);
|
|
208
|
+
setIsPreviewAnimationPaused(true);
|
|
209
|
+
|
|
210
|
+
if (previewMode === 'css3d') {
|
|
211
|
+
console.log('CSS3D initialization will happen after pre-rendering');
|
|
212
|
+
} else {
|
|
213
|
+
if (settings && typeof settings.updateStatus === 'function') {
|
|
214
|
+
settings.updateStatus('Initializing 3D cube preview...', 'info');
|
|
215
|
+
} else {
|
|
216
|
+
showStatus('Initializing 3D cube preview...', 'info');
|
|
217
|
+
}
|
|
218
|
+
initThreeJsPreview(canvasContainer, renderIframe, currentMeshId, true);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
switch (previewMode) {
|
|
222
|
+
case 'css3d':
|
|
223
|
+
progressText.textContent = 'Analyzing CSS3D animation...';
|
|
224
|
+
startCss3dPreRendering(renderIframe, () => {
|
|
225
|
+
settings.updateStatus('Initializing CSS3D preview...', 'info');
|
|
226
|
+
import('three/examples/jsm/renderers/CSS3DRenderer.js')
|
|
227
|
+
.then(module => {
|
|
228
|
+
const { CSS3DRenderer, CSS3DObject } = module;
|
|
229
|
+
setupCSS3DScene(canvasContainer, renderIframe, CSS3DRenderer, CSS3DObject, currentMeshId, true);
|
|
230
|
+
});
|
|
231
|
+
}, progressBar, settings, previewPlane);
|
|
232
|
+
break;
|
|
233
|
+
|
|
234
|
+
case 'threejs':
|
|
235
|
+
default:
|
|
236
|
+
progressText.textContent = 'Pre-rendering animation...';
|
|
237
|
+
startImage2TexturePreRendering(renderIframe, () => {
|
|
238
|
+
// Pre-rendering complete
|
|
239
|
+
}, progressBar, settings, previewPlane);
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
} catch (error) {
|
|
245
|
+
logPreviewError(`Preview error: ${error.message}`, previewContent, settings.errorContainer, settings.statusCallback);
|
|
246
|
+
settings.handleError('Error generating preview: ' + error.message);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Preview-specific animation loop wrapper
|
|
253
|
+
*/
|
|
254
|
+
export function animatePreview() {
|
|
255
|
+
// Preview-specific early exit check
|
|
256
|
+
if (!isPreviewActive) {
|
|
257
|
+
console.log('Preview no longer active, stopping animation loop');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Schedule next frame immediately for high priority
|
|
262
|
+
previewAnimationId = requestAnimationFrame(animatePreview);
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
// Prepare preview-specific settings
|
|
266
|
+
const animationType = currentPreviewSettings?.animationType || 'play';
|
|
267
|
+
const playbackSpeed = currentPreviewSettings?.playbackSpeed || 1.0;
|
|
268
|
+
|
|
269
|
+
// Create settings object for generic animation function
|
|
270
|
+
const animationSettings = {
|
|
271
|
+
animationType,
|
|
272
|
+
playbackSpeed,
|
|
273
|
+
targetFrameRate,
|
|
274
|
+
isPaused: isPreviewAnimationPaused
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// Create frame state object
|
|
278
|
+
const frameState = {
|
|
279
|
+
lastFrameTime: lastAnimationFrameTime,
|
|
280
|
+
frameInterval
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Call generic animation function
|
|
284
|
+
const newFrameTime = runAnimationFrame(
|
|
285
|
+
animationPreviewRenderer,
|
|
286
|
+
animationPreviewScene,
|
|
287
|
+
animationPreviewCamera,
|
|
288
|
+
previewPlane,
|
|
289
|
+
animationSettings,
|
|
290
|
+
frameState
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
// Update preview-specific frame time
|
|
294
|
+
if (newFrameTime !== null) {
|
|
295
|
+
lastAnimationFrameTime = newFrameTime;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.error('Error in preview animation loop:', error);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function resetPreviewAnimationId() {
|
|
304
|
+
previewAnimationId = null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function resetLastAnimationFrameTime() {
|
|
308
|
+
lastAnimationFrameTime = 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function setLastAnimationFrameTime(incomingValue) {
|
|
312
|
+
if(!incomingValue) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
lastAnimationFrameTime = incomingValue;
|
|
316
|
+
}
|