@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.
- 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,317 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import {
|
|
3
|
+
previewAnimationFrame,
|
|
4
|
+
previewCamera,
|
|
5
|
+
previewControls,
|
|
6
|
+
previewRenderer,
|
|
7
|
+
previewScene,
|
|
8
|
+
setPreviewAnimationFrame,
|
|
9
|
+
setPreviewCamera,
|
|
10
|
+
setPreviewControls,
|
|
11
|
+
setPreviewRenderer,
|
|
12
|
+
setPreviewScene
|
|
13
|
+
} from "../../state/glb-preview-state";
|
|
14
|
+
import { OrbitControls } from 'three/examples/jsm/Addons';
|
|
15
|
+
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a 3D preview of a GLB model
|
|
19
|
+
* @param {File} file - The GLB file to preview
|
|
20
|
+
* @param {HTMLElement} container - The container element to render the preview in
|
|
21
|
+
* @returns {Promise} A promise that resolves with the scene and renderer when preview is created
|
|
22
|
+
*/
|
|
23
|
+
export function createGLBPreview(file, container) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
if (!file || !container) {
|
|
26
|
+
reject(new Error('Missing required parameters for GLB preview'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Clean up any existing preview
|
|
31
|
+
cleanupPreview();
|
|
32
|
+
|
|
33
|
+
// Create the preview elements
|
|
34
|
+
container.innerHTML = '';
|
|
35
|
+
container.style.position = 'relative';
|
|
36
|
+
|
|
37
|
+
// Create loading indicator
|
|
38
|
+
const loadingIndicator = document.createElement('div');
|
|
39
|
+
loadingIndicator.className = 'preview-loading';
|
|
40
|
+
loadingIndicator.innerHTML = `
|
|
41
|
+
<div class="preview-loading-spinner"></div>
|
|
42
|
+
<div class="preview-loading-text">Loading model...</div>
|
|
43
|
+
`;
|
|
44
|
+
container.appendChild(loadingIndicator);
|
|
45
|
+
|
|
46
|
+
// Create renderer
|
|
47
|
+
setPreviewRenderer(new THREE.WebGLRenderer({ antialias: true, alpha: true }));
|
|
48
|
+
previewRenderer.setPixelRatio(window.devicePixelRatio);
|
|
49
|
+
previewRenderer.setClearColor(0x000000, 0);
|
|
50
|
+
previewRenderer.setSize(container.clientWidth, container.clientHeight);
|
|
51
|
+
previewRenderer.outputEncoding = THREE.sRGBEncoding;
|
|
52
|
+
container.appendChild(previewRenderer.domElement);
|
|
53
|
+
|
|
54
|
+
// Create scene
|
|
55
|
+
setPreviewScene(new THREE.Scene());
|
|
56
|
+
previewScene.background = new THREE.Color(0x111111);
|
|
57
|
+
|
|
58
|
+
// Add lighting
|
|
59
|
+
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
|
|
60
|
+
previewScene.add(ambientLight);
|
|
61
|
+
|
|
62
|
+
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
|
63
|
+
directionalLight.position.set(1, 2, 3);
|
|
64
|
+
previewScene.add(directionalLight);
|
|
65
|
+
|
|
66
|
+
// Create camera
|
|
67
|
+
setPreviewCamera(new THREE.PerspectiveCamera(
|
|
68
|
+
45, container.clientWidth / container.clientHeight, 0.1, 100
|
|
69
|
+
));
|
|
70
|
+
previewCamera.position.set(0, 0, 2);
|
|
71
|
+
|
|
72
|
+
// Create controls
|
|
73
|
+
setPreviewControls(new OrbitControls(previewCamera, previewRenderer.domElement));
|
|
74
|
+
previewControls.enableDamping = true;
|
|
75
|
+
previewControls.dampingFactor = 0.1;
|
|
76
|
+
previewControls.rotateSpeed = 0.8;
|
|
77
|
+
previewControls.enableZoom = true;
|
|
78
|
+
previewControls.zoomSpeed = 0.5;
|
|
79
|
+
previewControls.enablePan = false;
|
|
80
|
+
|
|
81
|
+
// Create a URL for the model file
|
|
82
|
+
const modelUrl = URL.createObjectURL(file);
|
|
83
|
+
|
|
84
|
+
// Load the model
|
|
85
|
+
const loader = new GLTFLoader();
|
|
86
|
+
loader.load(
|
|
87
|
+
modelUrl,
|
|
88
|
+
(gltf) => {
|
|
89
|
+
// Remove loading indicator
|
|
90
|
+
loadingIndicator.remove();
|
|
91
|
+
|
|
92
|
+
// Add the model to the scene
|
|
93
|
+
const model = gltf.scene;
|
|
94
|
+
|
|
95
|
+
// Calculate bounding box to properly scale and center the model
|
|
96
|
+
const box = new THREE.Box3().setFromObject(model);
|
|
97
|
+
const size = box.getSize(new THREE.Vector3());
|
|
98
|
+
const center = box.getCenter(new THREE.Vector3());
|
|
99
|
+
|
|
100
|
+
// Reset position to center
|
|
101
|
+
model.position.x = -center.x;
|
|
102
|
+
model.position.y = -center.y;
|
|
103
|
+
model.position.z = -center.z;
|
|
104
|
+
|
|
105
|
+
// Scale to fit
|
|
106
|
+
const maxDim = Math.max(size.x, size.y, size.z);
|
|
107
|
+
if (maxDim > 0) {
|
|
108
|
+
const scale = 1.5 / maxDim; // Scale to fit within a 1.5 unit sphere
|
|
109
|
+
model.scale.multiplyScalar(scale);
|
|
110
|
+
|
|
111
|
+
// Update bounding box after scaling
|
|
112
|
+
box.setFromObject(model);
|
|
113
|
+
box.getSize(size);
|
|
114
|
+
box.getCenter(center);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Create center group to help with positioning
|
|
118
|
+
const modelGroup = new THREE.Group();
|
|
119
|
+
modelGroup.add(model);
|
|
120
|
+
|
|
121
|
+
// Position the model on the grid properly
|
|
122
|
+
// Find the lowest point of the model after centering
|
|
123
|
+
const minY = box.min.y;
|
|
124
|
+
|
|
125
|
+
// If the model's minY isn't at the grid level, adjust it
|
|
126
|
+
if (minY !== 0) {
|
|
127
|
+
// Move the model so its bottom is just above the grid
|
|
128
|
+
model.position.y -= minY - 0.01; // Slight offset to avoid z-fighting
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Update the bounding box after repositioning
|
|
132
|
+
box.setFromObject(model);
|
|
133
|
+
box.getSize(size);
|
|
134
|
+
box.getCenter(center);
|
|
135
|
+
|
|
136
|
+
// Add to scene
|
|
137
|
+
previewScene.add(modelGroup);
|
|
138
|
+
|
|
139
|
+
// Add a grid helper
|
|
140
|
+
const gridSize = Math.max(2, Math.ceil(Math.max(size.x, size.z) * 1.5));
|
|
141
|
+
const gridHelper = new THREE.GridHelper(gridSize, gridSize * 2, 0x888888, 0x444444);
|
|
142
|
+
// Position grid at y=0 (standard ground plane)
|
|
143
|
+
gridHelper.position.y = 0;
|
|
144
|
+
previewScene.add(gridHelper);
|
|
145
|
+
|
|
146
|
+
// Calculate optimal camera position to ensure the model is fully visible
|
|
147
|
+
const fov = previewCamera.fov * (Math.PI / 180); // convert to radians
|
|
148
|
+
const aspectRatio = container.clientWidth / container.clientHeight;
|
|
149
|
+
|
|
150
|
+
// Get model dimensions after scaling
|
|
151
|
+
const scaledSizeX = size.x;
|
|
152
|
+
const scaledSizeY = size.y;
|
|
153
|
+
const scaledSizeZ = size.z;
|
|
154
|
+
|
|
155
|
+
// Calculate the center point of the model for camera targeting
|
|
156
|
+
// This should be the geometric center, not just (0,0,0)
|
|
157
|
+
const modelCenter = new THREE.Vector3(0, box.getCenter(new THREE.Vector3()).y, 0);
|
|
158
|
+
|
|
159
|
+
// Calculate required distance for each dimension
|
|
160
|
+
// For Z dimension, we need to consider the model's depth and our angle of view
|
|
161
|
+
const distanceForHeight = scaledSizeY / (2 * Math.tan(fov / 2));
|
|
162
|
+
const distanceForWidth = scaledSizeX / (2 * Math.tan(fov / 2) * aspectRatio);
|
|
163
|
+
const distanceForDepth = scaledSizeZ * 1.2; // Add more space for depth
|
|
164
|
+
|
|
165
|
+
// Base distance calculation
|
|
166
|
+
let optimalDistance = Math.max(distanceForWidth, distanceForHeight, distanceForDepth);
|
|
167
|
+
|
|
168
|
+
// Detect extreme model shapes and adjust accordingly
|
|
169
|
+
const aspectRatioXY = scaledSizeX / scaledSizeY;
|
|
170
|
+
const aspectRatioXZ = scaledSizeX / scaledSizeZ;
|
|
171
|
+
const aspectRatioYZ = scaledSizeY / scaledSizeZ;
|
|
172
|
+
|
|
173
|
+
// Add extra buffer for camera distance
|
|
174
|
+
let bufferMultiplier = 1.6;
|
|
175
|
+
|
|
176
|
+
// Add more buffer for flat/wide models (high aspect ratios)
|
|
177
|
+
if (aspectRatioXY > 4 || aspectRatioXY < 0.25 ||
|
|
178
|
+
aspectRatioXZ > 4 || aspectRatioXZ < 0.25 ||
|
|
179
|
+
aspectRatioYZ > 4 || aspectRatioYZ < 0.25) {
|
|
180
|
+
bufferMultiplier = 2.0; // More space for extreme shapes
|
|
181
|
+
console.log('Extreme model shape detected - using larger buffer');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Apply buffer to optimal distance
|
|
185
|
+
optimalDistance *= bufferMultiplier;
|
|
186
|
+
|
|
187
|
+
// Set minimum distance
|
|
188
|
+
const finalDistance = Math.max(optimalDistance, 2.5);
|
|
189
|
+
|
|
190
|
+
// Calculate X and Y offsets for perspective view
|
|
191
|
+
const xOffset = finalDistance * 0.4;
|
|
192
|
+
let yOffset = finalDistance * 0.3;
|
|
193
|
+
|
|
194
|
+
// Auto orient the model for better visibility for flat models
|
|
195
|
+
if (scaledSizeZ < scaledSizeX * 0.25 && scaledSizeZ < scaledSizeY * 0.25) {
|
|
196
|
+
// Model is very flat - orient it to face the camera better
|
|
197
|
+
modelGroup.rotation.x = -Math.PI / 12; // Slight tilt
|
|
198
|
+
|
|
199
|
+
// Adjust model position to remain on grid after tilting
|
|
200
|
+
const elevationOffset = scaledSizeX * Math.sin(Math.PI / 12) / 2;
|
|
201
|
+
model.position.y += elevationOffset;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Calculate optimal camera target (vertical center of the model)
|
|
205
|
+
// This ensures the camera is looking at the middle of the model, not the bottom
|
|
206
|
+
const targetY = modelCenter.y;
|
|
207
|
+
|
|
208
|
+
// Special handling for very tall models
|
|
209
|
+
if (scaledSizeY > scaledSizeX * 3 && scaledSizeY > scaledSizeZ * 3) {
|
|
210
|
+
// For very tall models, position camera higher to see from middle
|
|
211
|
+
yOffset = Math.min(targetY + finalDistance * 0.2, finalDistance * 0.7);
|
|
212
|
+
} else if (scaledSizeY < 0.5) {
|
|
213
|
+
// For very flat horizontal models, don't position camera too low
|
|
214
|
+
yOffset = Math.max(targetY * 2, finalDistance * 0.2);
|
|
215
|
+
} else {
|
|
216
|
+
// For normal models, position camera to see the entire height
|
|
217
|
+
// We need to raise the camera enough to see the top of the model
|
|
218
|
+
yOffset = Math.max(targetY, finalDistance * 0.2);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Final camera positioning - position relative to the model's center point
|
|
222
|
+
previewCamera.position.set(
|
|
223
|
+
xOffset,
|
|
224
|
+
yOffset,
|
|
225
|
+
finalDistance
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Ensure controls target is at the center of the model (y-center, not ground level)
|
|
229
|
+
previewControls.target.set(0, targetY, 0);
|
|
230
|
+
previewControls.update();
|
|
231
|
+
|
|
232
|
+
// Animation loop - only updates controls, no auto-rotation
|
|
233
|
+
const animate = () => {
|
|
234
|
+
setPreviewAnimationFrame(requestAnimationFrame(animate));
|
|
235
|
+
previewControls.update();
|
|
236
|
+
previewRenderer.render(previewScene, previewCamera);
|
|
237
|
+
};
|
|
238
|
+
animate();
|
|
239
|
+
|
|
240
|
+
// Handle window resize
|
|
241
|
+
const handleResize = () => {
|
|
242
|
+
if (!container) return;
|
|
243
|
+
|
|
244
|
+
const width = container.clientWidth;
|
|
245
|
+
const height = container.clientHeight;
|
|
246
|
+
|
|
247
|
+
previewCamera.aspect = width / height;
|
|
248
|
+
previewCamera.updateProjectionMatrix();
|
|
249
|
+
|
|
250
|
+
previewRenderer.setSize(width, height);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
window.addEventListener('resize', handleResize);
|
|
254
|
+
|
|
255
|
+
// Clean up object URL and event listener when done
|
|
256
|
+
const cleanup = () => {
|
|
257
|
+
URL.revokeObjectURL(modelUrl);
|
|
258
|
+
window.removeEventListener('resize', handleResize);
|
|
259
|
+
cleanupPreview();
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Resolve with the scene, model, and cleanup function
|
|
263
|
+
resolve({
|
|
264
|
+
scene: previewScene,
|
|
265
|
+
camera: previewCamera,
|
|
266
|
+
renderer: previewRenderer,
|
|
267
|
+
controls: previewControls,
|
|
268
|
+
model: model,
|
|
269
|
+
gltf: gltf,
|
|
270
|
+
cleanup: cleanup
|
|
271
|
+
});
|
|
272
|
+
},
|
|
273
|
+
(xhr) => {
|
|
274
|
+
// Update loading progress
|
|
275
|
+
const percent = Math.floor((xhr.loaded / xhr.total) * 100);
|
|
276
|
+
loadingIndicator.querySelector('.preview-loading-text').textContent =
|
|
277
|
+
`Loading model... ${percent}%`;
|
|
278
|
+
},
|
|
279
|
+
(error) => {
|
|
280
|
+
console.error('Error loading model:', error);
|
|
281
|
+
loadingIndicator.remove();
|
|
282
|
+
|
|
283
|
+
// Show error message
|
|
284
|
+
const errorMsg = document.createElement('div');
|
|
285
|
+
errorMsg.className = 'no-image-message-container visible';
|
|
286
|
+
errorMsg.textContent = 'Error loading model. Please try another file.';
|
|
287
|
+
container.appendChild(errorMsg);
|
|
288
|
+
|
|
289
|
+
// Reject the promise with the error
|
|
290
|
+
reject(error);
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Clean up the preview resources
|
|
298
|
+
*/
|
|
299
|
+
function cleanupPreview() {
|
|
300
|
+
if (previewAnimationFrame) {
|
|
301
|
+
cancelAnimationFrame(previewAnimationFrame);
|
|
302
|
+
setPreviewAnimationFrame(null);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (previewControls) {
|
|
306
|
+
previewControls.dispose();
|
|
307
|
+
setPreviewControls(null);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (previewRenderer) {
|
|
311
|
+
previewRenderer.dispose();
|
|
312
|
+
setPreviewRenderer(null);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
setPreviewScene(null);
|
|
316
|
+
setPreviewCamera(null);
|
|
317
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { updateState } from '../../state/scene-state';
|
|
2
|
+
import { processLightingFile } from '../../workers/worker-manager';
|
|
3
|
+
import * as worldPanelModule from '../../../panels/world-panel/world-panel';
|
|
4
|
+
import { hidePreviewLoading, showPreviewLoading } from '../../../loading-splash/preview-loading-splash';
|
|
5
|
+
import { formatFileSize } from './file-upload-manager';
|
|
6
|
+
import { createClearButton } from '../../../landing-page/landing-page';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Handle lighting file upload
|
|
10
|
+
* @param {File} file - The uploaded file
|
|
11
|
+
* @param {HTMLElement} infoElement - Element to display file info
|
|
12
|
+
* @param {HTMLElement} previewElement - Element to display file preview
|
|
13
|
+
* @param {HTMLElement} dropzone - The dropzone element
|
|
14
|
+
*/
|
|
15
|
+
export function handleLightingUpload(file, infoElement, previewElement, dropzone) {
|
|
16
|
+
// Store the file in the state
|
|
17
|
+
updateState('lightingFile', file);
|
|
18
|
+
|
|
19
|
+
// Store original h3 title
|
|
20
|
+
const originalTitle = dropzone.querySelector('h3').textContent;
|
|
21
|
+
|
|
22
|
+
// Mark dropzone as having a file
|
|
23
|
+
dropzone.classList.add('has-file');
|
|
24
|
+
|
|
25
|
+
// Clear the entire dropzone content
|
|
26
|
+
dropzone.innerHTML = '';
|
|
27
|
+
|
|
28
|
+
// Add back just the title as a header
|
|
29
|
+
const titleElement = document.createElement('h3');
|
|
30
|
+
titleElement.textContent = originalTitle;
|
|
31
|
+
dropzone.appendChild(titleElement);
|
|
32
|
+
|
|
33
|
+
// Add the clear button using the shared function
|
|
34
|
+
dropzone.appendChild(createClearButton(dropzone, 'lighting', originalTitle));
|
|
35
|
+
|
|
36
|
+
// Add file info
|
|
37
|
+
infoElement = document.createElement('p');
|
|
38
|
+
infoElement.className = 'file-info';
|
|
39
|
+
infoElement.textContent = `${file.name} (${formatFileSize(file.size)})`;
|
|
40
|
+
dropzone.appendChild(infoElement);
|
|
41
|
+
|
|
42
|
+
// Create a container for the preview that will hold both the canvas and the loading indicator
|
|
43
|
+
const previewDiv = document.createElement('div');
|
|
44
|
+
previewDiv.className = 'preview';
|
|
45
|
+
dropzone.appendChild(previewDiv);
|
|
46
|
+
|
|
47
|
+
const containerDiv = document.createElement('div');
|
|
48
|
+
containerDiv.className = 'hdr-preview-container';
|
|
49
|
+
|
|
50
|
+
// Add event listener to prevent click events from reaching the dropzone
|
|
51
|
+
containerDiv.addEventListener('click', (e) => {
|
|
52
|
+
e.stopPropagation();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Add event listener to prevent mousedown events to avoid accidental drag interactions
|
|
56
|
+
containerDiv.addEventListener('mousedown', (e) => {
|
|
57
|
+
e.stopPropagation();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
previewDiv.appendChild(containerDiv);
|
|
61
|
+
|
|
62
|
+
// Show loading state directly on the container
|
|
63
|
+
showPreviewLoading(containerDiv);
|
|
64
|
+
|
|
65
|
+
// Create canvas for the preview with appropriate size but keep it hidden initially
|
|
66
|
+
const canvas = document.createElement('canvas');
|
|
67
|
+
canvas.className = 'hdr-preview-canvas';
|
|
68
|
+
|
|
69
|
+
// Make canvas dimensions equal for a square aspect ratio
|
|
70
|
+
const previewSize = 256;
|
|
71
|
+
canvas.width = previewSize;
|
|
72
|
+
canvas.height = previewSize;
|
|
73
|
+
|
|
74
|
+
canvas.classList.add('hidden'); // Initially hidden until loaded
|
|
75
|
+
|
|
76
|
+
// Create a message element for errors/status
|
|
77
|
+
const messageDiv = document.createElement('div');
|
|
78
|
+
messageDiv.className = 'no-image-message-container hidden';
|
|
79
|
+
|
|
80
|
+
// Add elements to the container
|
|
81
|
+
containerDiv.appendChild(canvas);
|
|
82
|
+
containerDiv.appendChild(messageDiv);
|
|
83
|
+
|
|
84
|
+
// Process the lighting file in a web worker
|
|
85
|
+
processLightingFile(file)
|
|
86
|
+
.then(result => {
|
|
87
|
+
// Use the worker result to process the lighting file
|
|
88
|
+
const fileType = result.fileType;
|
|
89
|
+
const arrayBuffer = result.arrayBuffer;
|
|
90
|
+
|
|
91
|
+
// For EXR files
|
|
92
|
+
if (fileType === 'exr') {
|
|
93
|
+
import('three').then(THREE => {
|
|
94
|
+
import('three/addons/loaders/EXRLoader.js').then(({ EXRLoader }) => {
|
|
95
|
+
const loader = new EXRLoader();
|
|
96
|
+
loader.setDataType(THREE.FloatType);
|
|
97
|
+
|
|
98
|
+
// Create a Blob from the array buffer
|
|
99
|
+
const blob = new Blob([arrayBuffer], { type: 'application/octet-stream' });
|
|
100
|
+
const url = URL.createObjectURL(blob);
|
|
101
|
+
|
|
102
|
+
loader.load(url, texture => {
|
|
103
|
+
// Show the canvas
|
|
104
|
+
canvas.classList.add('visible');
|
|
105
|
+
canvas.classList.remove('hidden');
|
|
106
|
+
|
|
107
|
+
// Create a sphere preview with proper controls
|
|
108
|
+
worldPanelModule.createSpherePreview(THREE, texture, canvas, messageDiv);
|
|
109
|
+
|
|
110
|
+
// Clean up URL after loading
|
|
111
|
+
URL.revokeObjectURL(url);
|
|
112
|
+
|
|
113
|
+
// Hide loading indicator
|
|
114
|
+
hidePreviewLoading(containerDiv);
|
|
115
|
+
|
|
116
|
+
// Store the lighting texture for use in previews
|
|
117
|
+
updateState('environmentTexture', texture);
|
|
118
|
+
}, undefined, error => {
|
|
119
|
+
console.error('Error loading EXR texture:', error);
|
|
120
|
+
canvas.classList.add('visible');
|
|
121
|
+
canvas.classList.remove('hidden');
|
|
122
|
+
hidePreviewLoading(containerDiv);
|
|
123
|
+
if (messageDiv) {
|
|
124
|
+
messageDiv.classList.remove('hidden');
|
|
125
|
+
messageDiv.classList.add('visible');
|
|
126
|
+
messageDiv.textContent = 'Error loading EXR file';
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}).catch(handleLightingError);
|
|
130
|
+
}).catch(handleLightingError);
|
|
131
|
+
}
|
|
132
|
+
// For HDR files
|
|
133
|
+
else if (fileType === 'hdr') {
|
|
134
|
+
import('three').then(THREE => {
|
|
135
|
+
import('three/addons/loaders/RGBELoader.js').then(({ RGBELoader }) => {
|
|
136
|
+
const loader = new RGBELoader();
|
|
137
|
+
|
|
138
|
+
// Create a Blob from the array buffer
|
|
139
|
+
const blob = new Blob([arrayBuffer], { type: 'application/octet-stream' });
|
|
140
|
+
const url = URL.createObjectURL(blob);
|
|
141
|
+
|
|
142
|
+
loader.load(url, texture => {
|
|
143
|
+
// Show the canvas
|
|
144
|
+
canvas.classList.add('visible');
|
|
145
|
+
canvas.classList.remove('hidden');
|
|
146
|
+
|
|
147
|
+
// Create a sphere preview with proper controls
|
|
148
|
+
worldPanelModule.createSpherePreview(THREE, texture, canvas, messageDiv);
|
|
149
|
+
|
|
150
|
+
// Clean up URL after loading
|
|
151
|
+
URL.revokeObjectURL(url);
|
|
152
|
+
|
|
153
|
+
// Hide loading indicator
|
|
154
|
+
hidePreviewLoading(containerDiv);
|
|
155
|
+
|
|
156
|
+
// Store the lighting texture for use in previews
|
|
157
|
+
updateState('environmentTexture', texture);
|
|
158
|
+
}, undefined, error => {
|
|
159
|
+
console.error('Error loading HDR texture:', error);
|
|
160
|
+
canvas.classList.add('visible');
|
|
161
|
+
canvas.classList.remove('hidden');
|
|
162
|
+
hidePreviewLoading(containerDiv);
|
|
163
|
+
if (messageDiv) {
|
|
164
|
+
messageDiv.classList.remove('hidden');
|
|
165
|
+
messageDiv.classList.add('visible');
|
|
166
|
+
messageDiv.textContent = 'Error loading HDR file';
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}).catch(handleLightingError);
|
|
170
|
+
}).catch(handleLightingError);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
handleLightingError(new Error('Unsupported file type: ' + fileType));
|
|
174
|
+
return -1;
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
.catch(error => {
|
|
178
|
+
console.error('Error processing lighting file:', error);
|
|
179
|
+
handleLightingError(error);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Helper function to handle lighting errors
|
|
183
|
+
function handleLightingError(error) {
|
|
184
|
+
console.error('Lighting error:', error);
|
|
185
|
+
canvas.classList.add('visible');
|
|
186
|
+
canvas.classList.remove('hidden');
|
|
187
|
+
hidePreviewLoading(containerDiv);
|
|
188
|
+
if (messageDiv) {
|
|
189
|
+
messageDiv.classList.remove('hidden');
|
|
190
|
+
messageDiv.classList.add('visible');
|
|
191
|
+
messageDiv.textContent = 'Error loading lighting file';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createClearButton } from "../../../landing-page/landing-page";
|
|
2
|
+
import { hidePreviewLoading, showPreviewLoading } from "../../../loading-splash/preview-loading-splash";
|
|
3
|
+
import { updateState } from "../../state/scene-state";
|
|
4
|
+
import { formatFileSize } from "./file-upload-manager";
|
|
5
|
+
import { createGLBPreview } from "./glb-preview-controller";
|
|
6
|
+
import { processGLBFile } from "./glb-file-handler";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Handle model file upload
|
|
10
|
+
* @param {File} file - The uploaded file
|
|
11
|
+
* @param {HTMLElement} infoElement - Element to display file info
|
|
12
|
+
* @param {HTMLElement} dropzone - The dropzone element
|
|
13
|
+
*/
|
|
14
|
+
export function handleModelUpload(file, infoElement, dropzone) {
|
|
15
|
+
// Store the file in the state with a single update
|
|
16
|
+
updateState({
|
|
17
|
+
modelFile: file,
|
|
18
|
+
useCustomModel: true
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// If dropzone is null, find it by ID
|
|
22
|
+
if (!dropzone) {
|
|
23
|
+
console.log("Dropzone parameter is null, attempting to find model dropzone by ID");
|
|
24
|
+
dropzone = document.getElementById('model-dropzone');
|
|
25
|
+
|
|
26
|
+
// If still null, just update state and return early
|
|
27
|
+
if (!dropzone) {
|
|
28
|
+
console.error("Could not find model-dropzone element, skipping UI update");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Store original h3 title
|
|
34
|
+
const originalTitle = dropzone.querySelector('h3').textContent;
|
|
35
|
+
|
|
36
|
+
// Mark dropzone as having a file
|
|
37
|
+
dropzone.classList.add('has-file');
|
|
38
|
+
|
|
39
|
+
// Clear the entire dropzone content
|
|
40
|
+
dropzone.innerHTML = '';
|
|
41
|
+
|
|
42
|
+
// Add back just the title as a header
|
|
43
|
+
const titleElement = document.createElement('h3');
|
|
44
|
+
titleElement.textContent = originalTitle;
|
|
45
|
+
dropzone.appendChild(titleElement);
|
|
46
|
+
|
|
47
|
+
// Add the clear button using the shared function
|
|
48
|
+
dropzone.appendChild(createClearButton(dropzone, 'model', originalTitle));
|
|
49
|
+
|
|
50
|
+
// Add file info
|
|
51
|
+
infoElement = document.createElement('p');
|
|
52
|
+
infoElement.className = 'file-info';
|
|
53
|
+
infoElement.id = 'model-info';
|
|
54
|
+
infoElement.textContent = `${file.name} (${formatFileSize(file.size)})`;
|
|
55
|
+
dropzone.appendChild(infoElement);
|
|
56
|
+
|
|
57
|
+
// Create a preview container
|
|
58
|
+
const previewDiv = document.createElement('div');
|
|
59
|
+
previewDiv.className = 'preview model-preview-container';
|
|
60
|
+
previewDiv.id = 'model-preview';
|
|
61
|
+
|
|
62
|
+
// Add event listener to prevent click events from reaching the dropzone
|
|
63
|
+
previewDiv.addEventListener('click', (e) => {
|
|
64
|
+
e.stopPropagation();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Add event listener to prevent mousedown events to avoid accidental drag interactions
|
|
68
|
+
previewDiv.addEventListener('mousedown', (e) => {
|
|
69
|
+
e.stopPropagation();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
dropzone.appendChild(previewDiv);
|
|
73
|
+
|
|
74
|
+
// Show loading state
|
|
75
|
+
showPreviewLoading(previewDiv);
|
|
76
|
+
|
|
77
|
+
// Process the model file using our new GLB utility
|
|
78
|
+
processGLBFile(file)
|
|
79
|
+
.then(result => {
|
|
80
|
+
// Create the 3D preview with our new GLB utility
|
|
81
|
+
return createGLBPreview(file, previewDiv);
|
|
82
|
+
})
|
|
83
|
+
.then(result => {
|
|
84
|
+
// Hide loading indicator
|
|
85
|
+
hidePreviewLoading(previewDiv);
|
|
86
|
+
|
|
87
|
+
// Update the texture dropzone hints to show textures are optional with GLB
|
|
88
|
+
const textureHints = document.querySelectorAll('.texture-hint');
|
|
89
|
+
textureHints.forEach(hint => {
|
|
90
|
+
hint.textContent = 'Textures are optional with GLB';
|
|
91
|
+
hint.classList.add('optional');
|
|
92
|
+
});
|
|
93
|
+
})
|
|
94
|
+
.catch(error => {
|
|
95
|
+
console.error('Error processing model file:', error);
|
|
96
|
+
hidePreviewLoading(previewDiv);
|
|
97
|
+
|
|
98
|
+
// Show error message in preview
|
|
99
|
+
const errorMsg = document.createElement('div');
|
|
100
|
+
errorMsg.className = 'no-image-message-container visible';
|
|
101
|
+
errorMsg.textContent = 'Error loading model. Please try another file.';
|
|
102
|
+
previewDiv.appendChild(errorMsg);
|
|
103
|
+
});
|
|
104
|
+
}
|