@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,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
+ }