@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,1891 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset Debugger - World Panel Module
|
|
3
|
+
*
|
|
4
|
+
* This module handles world properties, environment, and lighting visualization and controls.
|
|
5
|
+
*/
|
|
6
|
+
import { getState } from '../../util/state/scene-state';
|
|
7
|
+
import { updateLighting, resetLighting, updateExposure } from '../../util/scene/lighting-manager';
|
|
8
|
+
import * as THREE from 'three';
|
|
9
|
+
|
|
10
|
+
// Track initialization state
|
|
11
|
+
let controlsInitialized = false;
|
|
12
|
+
|
|
13
|
+
// Store environment (HDR/EXR) metadata for display
|
|
14
|
+
let currentLightingMetadata = null;
|
|
15
|
+
|
|
16
|
+
// Store background image metadata for display
|
|
17
|
+
let currentBackgroundMetadata = null;
|
|
18
|
+
|
|
19
|
+
// Store the environment texture for preview
|
|
20
|
+
let environmentTexture = null;
|
|
21
|
+
|
|
22
|
+
// Store the background texture for preview
|
|
23
|
+
let backgroundTexture = null;
|
|
24
|
+
|
|
25
|
+
// Track the currently selected background option
|
|
26
|
+
let currentBackgroundOption = null; // Initialize to null for proper first-time selection logic
|
|
27
|
+
|
|
28
|
+
// Track if an EXR/HDR file is currently being loaded
|
|
29
|
+
let exrLoadInProgress = false;
|
|
30
|
+
|
|
31
|
+
// Add a single flag to track when an EXR/HDR file is being loaded
|
|
32
|
+
let isLoadingEnvironmentFile = false;
|
|
33
|
+
|
|
34
|
+
// NEW: Track event listeners for proper cleanup
|
|
35
|
+
let eventListeners = [];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* NEW: Cleanup function to remove all event listeners
|
|
39
|
+
*/
|
|
40
|
+
function cleanupEventListeners() {
|
|
41
|
+
console.log('Cleaning up world panel event listeners...');
|
|
42
|
+
eventListeners.forEach(({ element, event, handler }) => {
|
|
43
|
+
if (element && typeof element.removeEventListener === 'function') {
|
|
44
|
+
element.removeEventListener(event, handler);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
eventListeners = [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* NEW: Helper to add tracked event listeners
|
|
52
|
+
*/
|
|
53
|
+
function addTrackedEventListener(element, event, handler) {
|
|
54
|
+
if (element && typeof element.addEventListener === 'function') {
|
|
55
|
+
element.addEventListener(event, handler);
|
|
56
|
+
eventListeners.push({ element, event, handler });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Set the scene background to null and save the current background if it exists
|
|
62
|
+
* @param {THREE.Scene} scene - The Three.js scene
|
|
63
|
+
*/
|
|
64
|
+
function setNullBackground(scene) {
|
|
65
|
+
if (scene.background) {
|
|
66
|
+
if (!scene.userData) scene.userData = {};
|
|
67
|
+
scene.userData.savedBackground = scene.background;
|
|
68
|
+
scene.background = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Determine the current background UI state based on available data
|
|
74
|
+
* This centralizes all the logic for determining what should be visible
|
|
75
|
+
* @returns {Object} UI state object with flags for visibility control
|
|
76
|
+
*/
|
|
77
|
+
function determineBackgroundUIState() {
|
|
78
|
+
const state = getState();
|
|
79
|
+
return {
|
|
80
|
+
// Background image state
|
|
81
|
+
hasBackgroundImage: Boolean(state.backgroundFile || state.backgroundTexture),
|
|
82
|
+
// Environment (HDR/EXR) state
|
|
83
|
+
hasEnvironment: Boolean(state.scene && state.scene.environment),
|
|
84
|
+
// Currently selected option
|
|
85
|
+
currentSelection: currentBackgroundOption || 'none',
|
|
86
|
+
// Detailed state data for debugging
|
|
87
|
+
details: {
|
|
88
|
+
backgroundFile: state.backgroundFile ?
|
|
89
|
+
`${state.backgroundFile.name} (${state.backgroundFile.type})` : null,
|
|
90
|
+
backgroundTexture: state.backgroundTexture ? 'present' : null,
|
|
91
|
+
environmentTexture: (state.scene && state.scene.environment) ? 'present' : null,
|
|
92
|
+
backgroundMetadata: currentBackgroundMetadata ? 'present' : null,
|
|
93
|
+
lightingMetadata: currentLightingMetadata ? 'present' : null
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Toggle visibility of a specific background option
|
|
100
|
+
* @param {string} optionId - The ID of the option element to toggle ('background-option' or 'hdr-option')
|
|
101
|
+
* @param {boolean} visible - Whether the option should be visible
|
|
102
|
+
*/
|
|
103
|
+
export function toggleOptionVisibility(optionId, visible) {
|
|
104
|
+
const option = document.getElementById(optionId);
|
|
105
|
+
if (!option) {
|
|
106
|
+
console.warn(`Cannot toggle visibility: Option with ID '${optionId}' not found`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Get input and label elements specifically (not all children)
|
|
111
|
+
const input = option.querySelector('input[type="radio"]');
|
|
112
|
+
const label = option.querySelector('label');
|
|
113
|
+
|
|
114
|
+
if (!input || !label) {
|
|
115
|
+
console.warn(`Radio input or label not found in option with ID '${optionId}'`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Set display style for the elements
|
|
120
|
+
// Radio buttons use inline-block, labels use inline
|
|
121
|
+
input.style.display = visible ? 'inline-block' : 'none';
|
|
122
|
+
label.style.display = visible ? 'inline' : 'none';
|
|
123
|
+
|
|
124
|
+
// For debugging
|
|
125
|
+
console.log(`Background option '${optionId}' visibility set to ${visible ? 'visible' : 'hidden'}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Update all background UI elements based on the current state
|
|
130
|
+
* @param {Object} uiState - The UI state object from determineBackgroundUIState
|
|
131
|
+
*/
|
|
132
|
+
function updateBackgroundUIVisibility(uiState) {
|
|
133
|
+
// Log the current state for debugging
|
|
134
|
+
console.debug('Updating background UI visibility with state:', uiState);
|
|
135
|
+
|
|
136
|
+
// 1. Update the preview canvas opacity
|
|
137
|
+
updateCanvasOpacity(currentBackgroundOption);
|
|
138
|
+
|
|
139
|
+
// 2. Update message visibility - show the "no data" message only if BOTH background types are missing
|
|
140
|
+
const hasAnyBackground = uiState.hasBackgroundImage || uiState.hasEnvironment;
|
|
141
|
+
toggleBackgroundMessages(hasAnyBackground);
|
|
142
|
+
|
|
143
|
+
// Helper function to update canvas opacity
|
|
144
|
+
function updateCanvasOpacity(selectedValue) {
|
|
145
|
+
const bgPreviewCanvas = document.getElementById('bg-preview-canvas');
|
|
146
|
+
const environmentPreviewCanvas = document.getElementById('hdr-preview-canvas');
|
|
147
|
+
|
|
148
|
+
if (bgPreviewCanvas) {
|
|
149
|
+
bgPreviewCanvas.style.opacity = (selectedValue === 'background') ? '1' : '0.3';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (environmentPreviewCanvas) {
|
|
153
|
+
environmentPreviewCanvas.style.opacity = (selectedValue === 'hdr') ? '1' : '0.3';
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Refresh all background UI based on current state
|
|
160
|
+
* This is the main entry point to update all background UI elements
|
|
161
|
+
*/
|
|
162
|
+
function refreshBackgroundUI() {
|
|
163
|
+
const uiState = determineBackgroundUIState();
|
|
164
|
+
updateBackgroundUIVisibility(uiState);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Initialize the World panel and cache DOM elements
|
|
169
|
+
* @param {boolean} [forceReset=false] - Force reset even if already initialized
|
|
170
|
+
*/
|
|
171
|
+
export function initWorldPanel(forceReset = false) {
|
|
172
|
+
// Only initialize if not already done, and only if we're in the debug phase
|
|
173
|
+
// where the panel was explicitly requested to be initialized
|
|
174
|
+
if (controlsInitialized && !forceReset) {
|
|
175
|
+
console.log('World Panel already initialized, skipping');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ALWAYS cleanup existing event listeners first
|
|
180
|
+
cleanupEventListeners();
|
|
181
|
+
|
|
182
|
+
// If we're already initialized but forcing a reset, just reset the critical state
|
|
183
|
+
if (controlsInitialized && forceReset) {
|
|
184
|
+
console.log('World Panel already initialized, but forcing reset of critical state');
|
|
185
|
+
// Reset background related state on reinitialization
|
|
186
|
+
currentBackgroundOption = 'none';
|
|
187
|
+
currentBackgroundMetadata = null;
|
|
188
|
+
currentLightingMetadata = null;
|
|
189
|
+
backgroundTexture = null;
|
|
190
|
+
environmentTexture = null;
|
|
191
|
+
|
|
192
|
+
// Reset radio button visibility
|
|
193
|
+
toggleOptionVisibility('background-option', false);
|
|
194
|
+
toggleOptionVisibility('hdr-option', false);
|
|
195
|
+
|
|
196
|
+
console.log('Continuing initialization to check for loaded files');
|
|
197
|
+
|
|
198
|
+
// Get current state to check for loaded files
|
|
199
|
+
const state = getState();
|
|
200
|
+
const hasEnvironmentTexture = state.lightingFile !== null || (state.scene && state.scene.environment);
|
|
201
|
+
const hasBackgroundTexture = state.backgroundTexture !== null || state.backgroundFile !== null;
|
|
202
|
+
|
|
203
|
+
// Show options based on available files
|
|
204
|
+
toggleOptionVisibility('hdr-option', hasEnvironmentTexture);
|
|
205
|
+
toggleOptionVisibility('background-option', hasBackgroundTexture);
|
|
206
|
+
console.log(`Updated radio visibility after reset - HDR: ${hasEnvironmentTexture}, Background: ${hasBackgroundTexture}`);
|
|
207
|
+
|
|
208
|
+
// Set up event listeners again
|
|
209
|
+
setupBgToggleListener();
|
|
210
|
+
|
|
211
|
+
// Also update the background UI
|
|
212
|
+
refreshBackgroundUI();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.debug('Initializing World Panel...');
|
|
217
|
+
|
|
218
|
+
// Look for world-tab (from world-panel.html) or world-tab-container (from debugger-scene.html)
|
|
219
|
+
const worldPanel = document.getElementById('world-tab') || document.getElementById('world-tab-container');
|
|
220
|
+
|
|
221
|
+
if (!worldPanel) {
|
|
222
|
+
console.error('World panel elements not found. Panel may not be loaded in DOM yet.');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.debug('World panel found, initializing...');
|
|
227
|
+
|
|
228
|
+
// Reset background related state on initialization
|
|
229
|
+
currentBackgroundOption = 'none';
|
|
230
|
+
currentBackgroundMetadata = null;
|
|
231
|
+
currentLightingMetadata = null;
|
|
232
|
+
backgroundTexture = null;
|
|
233
|
+
environmentTexture = null;
|
|
234
|
+
|
|
235
|
+
// Initially hide background and HDR options until content is loaded
|
|
236
|
+
toggleOptionVisibility('background-option', false);
|
|
237
|
+
toggleOptionVisibility('hdr-option', false);
|
|
238
|
+
|
|
239
|
+
// Initialize value displays on sliders and set up event listeners
|
|
240
|
+
document.querySelectorAll('.control-group input[type="range"]').forEach(slider => {
|
|
241
|
+
const valueDisplay = slider.previousElementSibling.querySelector('.value-display');
|
|
242
|
+
if (valueDisplay) {
|
|
243
|
+
valueDisplay.textContent = slider.value;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Add input event listener to update value display when dragging
|
|
247
|
+
const inputHandler = function() {
|
|
248
|
+
const valueDisplay = this.previousElementSibling.querySelector('.value-display');
|
|
249
|
+
if (valueDisplay) {
|
|
250
|
+
valueDisplay.textContent = this.value;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
addTrackedEventListener(slider, 'input', inputHandler);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Initialize collapsible functionality
|
|
258
|
+
const collapsibleHeaders = document.querySelectorAll('.collapsible-header');
|
|
259
|
+
if (collapsibleHeaders) {
|
|
260
|
+
collapsibleHeaders.forEach(header => {
|
|
261
|
+
const clickHandler = function() {
|
|
262
|
+
const content = this.nextElementSibling;
|
|
263
|
+
const indicator = this.querySelector('.collapse-indicator');
|
|
264
|
+
|
|
265
|
+
if (content.style.display === 'none') {
|
|
266
|
+
content.style.display = 'block';
|
|
267
|
+
indicator.textContent = '[-]';
|
|
268
|
+
} else {
|
|
269
|
+
content.style.display = 'none';
|
|
270
|
+
indicator.textContent = '[+]';
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
addTrackedEventListener(header, 'click', clickHandler);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Set up event listeners for lighting controls
|
|
279
|
+
setupLightingControls();
|
|
280
|
+
|
|
281
|
+
// Set up Background toggle event listener
|
|
282
|
+
setupBgToggleListener();
|
|
283
|
+
|
|
284
|
+
// Add event listener for background updates from background-util.js
|
|
285
|
+
const backgroundUpdateHandler = function(e) {
|
|
286
|
+
const texture = e.detail.texture;
|
|
287
|
+
if (texture) {
|
|
288
|
+
console.log('Background texture updated event received');
|
|
289
|
+
|
|
290
|
+
// Extract metadata from the texture
|
|
291
|
+
let width = 0;
|
|
292
|
+
let height = 0;
|
|
293
|
+
let type = 'Unknown Texture';
|
|
294
|
+
let fileName = 'Background Texture';
|
|
295
|
+
|
|
296
|
+
// Get dimensions
|
|
297
|
+
if (texture.image) {
|
|
298
|
+
width = texture.image.width || 0;
|
|
299
|
+
height = texture.image.height || 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Determine type
|
|
303
|
+
if (texture.isHDRTexture) {
|
|
304
|
+
type = 'HDR Texture';
|
|
305
|
+
} else if (texture.isCompressedTexture) {
|
|
306
|
+
type = 'Compressed Texture';
|
|
307
|
+
} else if (texture.isDataTexture) {
|
|
308
|
+
type = 'Data Texture';
|
|
309
|
+
} else if (texture.isTexture) {
|
|
310
|
+
type = 'Standard Texture';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Try to get filename from userData if available
|
|
314
|
+
if (texture.userData && texture.userData.fileName) {
|
|
315
|
+
fileName = texture.userData.fileName;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Create metadata
|
|
319
|
+
const metadata = {
|
|
320
|
+
fileName: fileName,
|
|
321
|
+
type: type,
|
|
322
|
+
dimensions: { width, height },
|
|
323
|
+
// Estimate file size based on dimensions and encoding
|
|
324
|
+
fileSizeBytes: (width * height * 4) // Rough estimate: pixels * 4 bytes per pixel
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Update the UI with the texture metadata
|
|
328
|
+
updateBackgroundInfo(metadata);
|
|
329
|
+
|
|
330
|
+
// Store the texture for preview rendering
|
|
331
|
+
backgroundTexture = texture;
|
|
332
|
+
|
|
333
|
+
// Render the preview using the texture
|
|
334
|
+
renderBackgroundPreview(texture);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
addTrackedEventListener(document, 'background-updated', backgroundUpdateHandler);
|
|
339
|
+
|
|
340
|
+
// Mark as initialized
|
|
341
|
+
controlsInitialized = true;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Set up event listeners for lighting controls
|
|
346
|
+
*/
|
|
347
|
+
function setupLightingControls() {
|
|
348
|
+
// Look for lighting control elements
|
|
349
|
+
const ambientIntensityControl = document.getElementById('ambient-light-intensity');
|
|
350
|
+
const directionalIntensityControl = document.getElementById('directional-light-intensity');
|
|
351
|
+
const exposureControl = document.getElementById('exposure-value');
|
|
352
|
+
const resetLightingButton = document.getElementById('reset-lighting');
|
|
353
|
+
|
|
354
|
+
// Log if elements are not found
|
|
355
|
+
if (!ambientIntensityControl) {
|
|
356
|
+
console.warn('Ambient light intensity control not found');
|
|
357
|
+
}
|
|
358
|
+
if (!directionalIntensityControl) {
|
|
359
|
+
console.warn('Directional light intensity control not found');
|
|
360
|
+
}
|
|
361
|
+
if (!exposureControl) {
|
|
362
|
+
console.warn('Exposure control not found');
|
|
363
|
+
}
|
|
364
|
+
if (!resetLightingButton) {
|
|
365
|
+
console.warn('Reset lighting button not found');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Set up ambient light intensity control
|
|
369
|
+
if (ambientIntensityControl) {
|
|
370
|
+
const ambientHandler = (e) => {
|
|
371
|
+
const value = parseFloat(e.target.value);
|
|
372
|
+
updateLighting({
|
|
373
|
+
ambient: { intensity: value }
|
|
374
|
+
});
|
|
375
|
+
// Update value display
|
|
376
|
+
const valueDisplay = e.target.previousElementSibling.querySelector('.value-display');
|
|
377
|
+
if (valueDisplay) {
|
|
378
|
+
valueDisplay.textContent = value.toFixed(1);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
addTrackedEventListener(ambientIntensityControl, 'input', ambientHandler);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Set up directional light intensity control
|
|
386
|
+
if (directionalIntensityControl) {
|
|
387
|
+
const directionalHandler = (e) => {
|
|
388
|
+
const value = parseFloat(e.target.value);
|
|
389
|
+
updateLighting({
|
|
390
|
+
directional: { intensity: value }
|
|
391
|
+
});
|
|
392
|
+
// Update value display
|
|
393
|
+
const valueDisplay = e.target.previousElementSibling.querySelector('.value-display');
|
|
394
|
+
if (valueDisplay) {
|
|
395
|
+
valueDisplay.textContent = value.toFixed(1);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
addTrackedEventListener(directionalIntensityControl, 'input', directionalHandler);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Set up exposure control
|
|
403
|
+
if (exposureControl) {
|
|
404
|
+
const exposureHandler = (e) => {
|
|
405
|
+
const value = parseFloat(e.target.value);
|
|
406
|
+
updateExposure(value);
|
|
407
|
+
// Update value display
|
|
408
|
+
const valueDisplay = e.target.previousElementSibling.querySelector('.value-display');
|
|
409
|
+
if (valueDisplay) {
|
|
410
|
+
valueDisplay.textContent = value.toFixed(1);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
addTrackedEventListener(exposureControl, 'input', exposureHandler);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Set up reset lighting button
|
|
418
|
+
if (resetLightingButton) {
|
|
419
|
+
const resetHandler = () => {
|
|
420
|
+
resetLighting();
|
|
421
|
+
|
|
422
|
+
// Reset control values if they exist
|
|
423
|
+
if (ambientIntensityControl) {
|
|
424
|
+
ambientIntensityControl.value = 0.5;
|
|
425
|
+
const valueDisplay = ambientIntensityControl.previousElementSibling.querySelector('.value-display');
|
|
426
|
+
if (valueDisplay) {
|
|
427
|
+
valueDisplay.textContent = '0.5';
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (directionalIntensityControl) {
|
|
432
|
+
directionalIntensityControl.value = 1.0;
|
|
433
|
+
const valueDisplay = directionalIntensityControl.previousElementSibling.querySelector('.value-display');
|
|
434
|
+
if (valueDisplay) {
|
|
435
|
+
valueDisplay.textContent = '1.0';
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (exposureControl) {
|
|
440
|
+
exposureControl.value = 1.0;
|
|
441
|
+
const valueDisplay = exposureControl.previousElementSibling.querySelector('.value-display');
|
|
442
|
+
if (valueDisplay) {
|
|
443
|
+
valueDisplay.textContent = '1.0';
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Don't clear lighting info if we have an environment texture
|
|
448
|
+
const state = getState();
|
|
449
|
+
const hasEnvironmentTexture = state.scene && state.scene.environment !== null;
|
|
450
|
+
|
|
451
|
+
if (!hasEnvironmentTexture) {
|
|
452
|
+
// Only clear lighting info if no environment texture is loaded
|
|
453
|
+
clearLightingInfo();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Update slider visibility based on whether environment is loaded
|
|
457
|
+
updateSliderVisibility(hasEnvironmentTexture);
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
addTrackedEventListener(resetLightingButton, 'click', resetHandler);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Initialize message visibility
|
|
464
|
+
updateLightingMessage();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Set up listener for Background radio button selection
|
|
469
|
+
*/
|
|
470
|
+
function setupBgToggleListener() {
|
|
471
|
+
// Find all background toggle radio buttons
|
|
472
|
+
const backgroundOptions = document.querySelectorAll('input[name="bg-option"]');
|
|
473
|
+
const state = getState();
|
|
474
|
+
|
|
475
|
+
// Initial selection based on loaded files
|
|
476
|
+
let initialOption = 'none'; // Default is none
|
|
477
|
+
|
|
478
|
+
// Always default to 'none' unless otherwise specified
|
|
479
|
+
const selectedRadio = document.querySelector(`input[name="bg-option"][value="${initialOption}"]`);
|
|
480
|
+
if (selectedRadio) {
|
|
481
|
+
selectedRadio.checked = true;
|
|
482
|
+
currentBackgroundOption = initialOption;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Process UI state based on available resources
|
|
486
|
+
const uiState = determineBackgroundUIState();
|
|
487
|
+
updateBackgroundUIVisibility(uiState);
|
|
488
|
+
|
|
489
|
+
// CRITICAL FIX: Remove any existing change listeners before adding new ones
|
|
490
|
+
backgroundOptions.forEach(option => {
|
|
491
|
+
// Clone the node to remove all existing event listeners
|
|
492
|
+
const newOption = option.cloneNode(true);
|
|
493
|
+
option.parentNode.replaceChild(newOption, option);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// Now get the fresh nodes and add new listeners
|
|
497
|
+
const freshBackgroundOptions = document.querySelectorAll('input[name="bg-option"]');
|
|
498
|
+
|
|
499
|
+
// Add event listeners to each radio button
|
|
500
|
+
freshBackgroundOptions.forEach(option => {
|
|
501
|
+
const changeHandler = function() {
|
|
502
|
+
if (this.checked) {
|
|
503
|
+
const selectedValue = this.value;
|
|
504
|
+
currentBackgroundOption = selectedValue;
|
|
505
|
+
|
|
506
|
+
console.log(`Background option changed to: ${selectedValue}`);
|
|
507
|
+
|
|
508
|
+
// Get scene from state
|
|
509
|
+
const state = getState();
|
|
510
|
+
if (!state.scene) {
|
|
511
|
+
console.error('Cannot update background - scene not found in state');
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Apply the appropriate background based on selection
|
|
516
|
+
switch (selectedValue) {
|
|
517
|
+
case 'none':
|
|
518
|
+
// Set background to null or a dark color
|
|
519
|
+
setNullBackground(state.scene);
|
|
520
|
+
console.log('Background set to none');
|
|
521
|
+
break;
|
|
522
|
+
|
|
523
|
+
case 'hdr':
|
|
524
|
+
// Only apply HDR/EXR background if we have one
|
|
525
|
+
if (state.scene.environment) {
|
|
526
|
+
console.log('Setting background to HDR/EXR environment map');
|
|
527
|
+
state.scene.background = state.scene.environment;
|
|
528
|
+
console.log('HDR/EXR background applied successfully:', state.scene.environment);
|
|
529
|
+
} else if (environmentTexture) {
|
|
530
|
+
// Try using the cached environment texture if the scene doesn't have one
|
|
531
|
+
console.log('Using cached environment texture');
|
|
532
|
+
state.scene.background = environmentTexture;
|
|
533
|
+
// Also set it as the environment if not already set
|
|
534
|
+
if (!state.scene.environment) {
|
|
535
|
+
state.scene.environment = environmentTexture;
|
|
536
|
+
}
|
|
537
|
+
} else if (state.lightingFile) {
|
|
538
|
+
// If we have a lighting file but no texture, try to reload it
|
|
539
|
+
console.log('Attempting to reload environment texture from lighting file');
|
|
540
|
+
|
|
541
|
+
// Import lighting utility and load the texture again
|
|
542
|
+
import('../../util/scene/lighting-manager.js').then(lightingModule => {
|
|
543
|
+
if (lightingModule.setupEnvironmentLighting && state.lightingFile) {
|
|
544
|
+
lightingModule.setupEnvironmentLighting(state.lightingFile)
|
|
545
|
+
.then(newTexture => {
|
|
546
|
+
if (newTexture) {
|
|
547
|
+
console.log('Successfully reloaded environment texture');
|
|
548
|
+
state.scene.background = newTexture;
|
|
549
|
+
state.scene.environment = newTexture;
|
|
550
|
+
environmentTexture = newTexture;
|
|
551
|
+
}
|
|
552
|
+
})
|
|
553
|
+
.catch(error => {
|
|
554
|
+
console.error('Error reloading environment texture:', error);
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
} else {
|
|
559
|
+
console.warn('No HDR/EXR environment map available');
|
|
560
|
+
// Fall back to null background but KEEP the radio selection
|
|
561
|
+
setNullBackground(state.scene);
|
|
562
|
+
}
|
|
563
|
+
break;
|
|
564
|
+
|
|
565
|
+
case 'background':
|
|
566
|
+
// Only apply background texture if we have one
|
|
567
|
+
if (state.backgroundTexture) {
|
|
568
|
+
console.log('Setting background to loaded background texture');
|
|
569
|
+
state.scene.background = state.backgroundTexture;
|
|
570
|
+
} else if (backgroundTexture) {
|
|
571
|
+
// Try using the cached background texture if the state doesn't have one
|
|
572
|
+
console.log('Using cached background texture');
|
|
573
|
+
state.scene.background = backgroundTexture;
|
|
574
|
+
} else {
|
|
575
|
+
console.warn('No background texture available');
|
|
576
|
+
// Fall back to null background but KEEP the radio selection
|
|
577
|
+
setNullBackground(state.scene);
|
|
578
|
+
}
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Update canvas opacity based on selection
|
|
583
|
+
updateCanvasOpacity(selectedValue);
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
// Add the event listener and track it for cleanup
|
|
588
|
+
addTrackedEventListener(option, 'change', changeHandler);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// Helper function to update canvas opacity based on selection
|
|
592
|
+
function updateCanvasOpacity(selectedValue) {
|
|
593
|
+
const bgPreviewCanvas = document.getElementById('bg-preview-canvas');
|
|
594
|
+
const environmentPreviewCanvas = document.getElementById('hdr-preview-canvas');
|
|
595
|
+
|
|
596
|
+
if (bgPreviewCanvas) {
|
|
597
|
+
bgPreviewCanvas.style.opacity = (selectedValue === 'background') ? '1' : '0.3';
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (environmentPreviewCanvas) {
|
|
601
|
+
environmentPreviewCanvas.style.opacity = (selectedValue === 'hdr') ? '1' : '0.3';
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Toggle visibility of background messages based on whether environment texture or background is loaded
|
|
608
|
+
* @param {boolean} hasContent - Whether environment texture or background is loaded
|
|
609
|
+
* @param {boolean} [forceShow=false] - Force show/hide the data info regardless of content status
|
|
610
|
+
*/
|
|
611
|
+
export function toggleBackgroundMessages(hasContent, forceShow = false) {
|
|
612
|
+
const noBackgroundMessage = document.querySelector('.no-background-message');
|
|
613
|
+
const backgroundDataInfo = document.querySelector('.background-data-info');
|
|
614
|
+
|
|
615
|
+
if (!noBackgroundMessage || !backgroundDataInfo) {
|
|
616
|
+
console.warn('Background message elements not found');
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (hasContent || forceShow) {
|
|
621
|
+
console.log("Revealing Background Data section in World Panel");
|
|
622
|
+
// We have content, so show the data info and hide the "no data" message
|
|
623
|
+
noBackgroundMessage.style.display = 'none';
|
|
624
|
+
backgroundDataInfo.style.display = 'block';
|
|
625
|
+
} else {
|
|
626
|
+
console.log("Hiding Background Data section in World Panel");
|
|
627
|
+
// No content, show the "no data" message and hide the data info
|
|
628
|
+
noBackgroundMessage.style.display = 'block';
|
|
629
|
+
backgroundDataInfo.style.display = 'none';
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Toggle visibility of lighting messages based on whether environment lighting is loaded
|
|
635
|
+
* @param {boolean} hasContent - Whether environment lighting is loaded
|
|
636
|
+
* @param {boolean} [forceShow=false] - Force show/hide the data info regardless of content status
|
|
637
|
+
*/
|
|
638
|
+
function toggleLightingMessages(hasContent, forceShow = false) {
|
|
639
|
+
const noDataMessage = document.querySelector('.no-data-message');
|
|
640
|
+
const lightingDataInfo = document.querySelector('.lighting-data-info');
|
|
641
|
+
|
|
642
|
+
if (!noDataMessage || !lightingDataInfo) {
|
|
643
|
+
console.warn('Lighting message elements not found');
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (hasContent || forceShow) {
|
|
648
|
+
noDataMessage.style.display = 'none';
|
|
649
|
+
lightingDataInfo.style.display = 'block';
|
|
650
|
+
} else {
|
|
651
|
+
noDataMessage.style.display = 'block';
|
|
652
|
+
lightingDataInfo.style.display = 'none';
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Update lighting message visibility based on whether environment lighting is loaded
|
|
658
|
+
*/
|
|
659
|
+
function updateLightingMessage() {
|
|
660
|
+
const state = getState();
|
|
661
|
+
|
|
662
|
+
const hasEnvironment = state.scene && state.scene.environment;
|
|
663
|
+
|
|
664
|
+
// Use the standardized toggle function
|
|
665
|
+
toggleLightingMessages(hasEnvironment);
|
|
666
|
+
|
|
667
|
+
// Update slider visibility based on environment presence
|
|
668
|
+
updateSliderVisibility(hasEnvironment);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Update slider visibility based on whether HDR/EXR is loaded
|
|
673
|
+
* @param {boolean} hasEnvironment - Whether environment lighting is loaded
|
|
674
|
+
*/
|
|
675
|
+
function updateSliderVisibility(hasEnvironment) {
|
|
676
|
+
const ambientControl = document.querySelector('.ambient-control');
|
|
677
|
+
const directionalControl = document.querySelector('.directional-control');
|
|
678
|
+
const exposureControl = document.querySelector('.exposure-control');
|
|
679
|
+
|
|
680
|
+
if (!ambientControl || !directionalControl || !exposureControl) {
|
|
681
|
+
console.warn('Could not find slider controls for visibility update');
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (hasEnvironment) {
|
|
686
|
+
// When HDR/EXR is loaded: hide ambient/directional, show exposure
|
|
687
|
+
ambientControl.style.display = 'none';
|
|
688
|
+
directionalControl.style.display = 'none';
|
|
689
|
+
exposureControl.style.display = 'flex';
|
|
690
|
+
} else {
|
|
691
|
+
// When no HDR/EXR is loaded: show ambient/directional, hide exposure
|
|
692
|
+
ambientControl.style.display = 'flex';
|
|
693
|
+
directionalControl.style.display = 'flex';
|
|
694
|
+
exposureControl.style.display = 'none';
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Update the lighting info display with metadata
|
|
700
|
+
* @param {Object} metadata - The HDR/EXR metadata
|
|
701
|
+
*/
|
|
702
|
+
export function updateLightingInfo(metadata) {
|
|
703
|
+
console.log('Updating lighting info with metadata:', metadata.fileName, metadata.type);
|
|
704
|
+
|
|
705
|
+
// Store the metadata for later use if the panel isn't ready yet
|
|
706
|
+
currentLightingMetadata = metadata;
|
|
707
|
+
|
|
708
|
+
// Try to initialize the panel if not already done
|
|
709
|
+
if (!controlsInitialized) {
|
|
710
|
+
console.warn('Panel not initialized yet, storing metadata for later');
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Find the UI elements
|
|
715
|
+
const filenameEl = document.getElementById('lighting-filename');
|
|
716
|
+
const typeEl = document.getElementById('lighting-type');
|
|
717
|
+
const resolutionEl = document.getElementById('lighting-resolution');
|
|
718
|
+
const sizeEl = document.getElementById('lighting-size');
|
|
719
|
+
const rangeEl = document.getElementById('lighting-range');
|
|
720
|
+
const luminanceEl = document.getElementById('lighting-luminance');
|
|
721
|
+
const softwareEl = document.getElementById('lighting-software');
|
|
722
|
+
|
|
723
|
+
// Make sure all elements exist
|
|
724
|
+
if (!filenameEl || !typeEl || !resolutionEl || !sizeEl || !rangeEl || !luminanceEl || !softwareEl) {
|
|
725
|
+
console.error('Cannot update lighting info: UI elements not found');
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Update the UI with metadata
|
|
730
|
+
filenameEl.textContent = metadata.fileName || '-';
|
|
731
|
+
typeEl.textContent = metadata.type || '-';
|
|
732
|
+
|
|
733
|
+
const width = metadata.dimensions?.width || 0;
|
|
734
|
+
const height = metadata.dimensions?.height || 0;
|
|
735
|
+
resolutionEl.textContent = (width && height) ? `${width} × ${height}` : '-';
|
|
736
|
+
|
|
737
|
+
const fileSizeMB = metadata.fileSizeBytes ? (metadata.fileSizeBytes / 1024 / 1024).toFixed(2) + ' MB' : '-';
|
|
738
|
+
sizeEl.textContent = fileSizeMB;
|
|
739
|
+
|
|
740
|
+
rangeEl.textContent = metadata.dynamicRange ? metadata.dynamicRange.toFixed(2) + ' stops' : '-';
|
|
741
|
+
luminanceEl.textContent = metadata.maxLuminance ? metadata.maxLuminance.toFixed(2) : '-';
|
|
742
|
+
softwareEl.textContent = metadata.creationSoftware || '-';
|
|
743
|
+
|
|
744
|
+
// Show the lighting info section and hide the no data message
|
|
745
|
+
console.log('Showing lighting data info and hiding no data message');
|
|
746
|
+
toggleLightingMessages(true);
|
|
747
|
+
|
|
748
|
+
// Make the HDR option visible since we have environment lighting
|
|
749
|
+
toggleOptionVisibility('hdr-option', true);
|
|
750
|
+
console.log('Making HDR/EXR radio option visible');
|
|
751
|
+
|
|
752
|
+
// Update slider visibility - we have HDR/EXR data
|
|
753
|
+
updateSliderVisibility(true);
|
|
754
|
+
|
|
755
|
+
// IMPORTANT: We no longer auto-select the HDR/EXR option
|
|
756
|
+
// The "None" option will remain selected by default
|
|
757
|
+
// Only update the radio option if it doesn't exist yet
|
|
758
|
+
console.log('HDR/EXR radio option is available but not auto-selected');
|
|
759
|
+
|
|
760
|
+
// Make sure any collapsible content is still properly collapsed
|
|
761
|
+
const metadataContents = document.querySelectorAll('.metadata-content');
|
|
762
|
+
if (metadataContents && metadataContents.length > 0) {
|
|
763
|
+
console.log('Ensuring collapsible content is collapsed initially');
|
|
764
|
+
metadataContents.forEach(content => {
|
|
765
|
+
content.style.display = 'none';
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
// Make sure any indicators show the right symbol
|
|
769
|
+
const indicators = document.querySelectorAll('.collapse-indicator');
|
|
770
|
+
if (indicators && indicators.length > 0) {
|
|
771
|
+
indicators.forEach(indicator => {
|
|
772
|
+
// Always set to '+' to indicate collapsed state
|
|
773
|
+
indicator.textContent = '[+]';
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Try to get environment texture and render it
|
|
779
|
+
const state = getState();
|
|
780
|
+
if (state.scene && state.scene.environment) {
|
|
781
|
+
console.log('Found environment texture in scene, rendering preview');
|
|
782
|
+
|
|
783
|
+
// Store the environment texture for later use
|
|
784
|
+
environmentTexture = state.scene.environment;
|
|
785
|
+
|
|
786
|
+
// Render the preview
|
|
787
|
+
renderEnvironmentPreview(environmentTexture);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Render the HDR/EXR environment texture preview on canvas
|
|
793
|
+
* @param {THREE.Texture} texture - The environment texture to render
|
|
794
|
+
* @param {HTMLCanvasElement} [externalCanvas] - Optional external canvas to render on
|
|
795
|
+
* @param {HTMLElement} [externalNoImageMessage] - Optional external message element
|
|
796
|
+
* @returns {boolean} - Whether preview was rendered successfully
|
|
797
|
+
*/
|
|
798
|
+
export function renderEnvironmentPreview(texture, externalCanvas, externalNoImageMessage) {
|
|
799
|
+
// Look for the canvas element - either use provided external one or find in DOM
|
|
800
|
+
const canvas = externalCanvas || document.getElementById('hdr-preview-canvas');
|
|
801
|
+
const noImageMessage = externalNoImageMessage || document.getElementById('no-image-message');
|
|
802
|
+
|
|
803
|
+
// If canvas not found, panel may not be initialized yet
|
|
804
|
+
if (!canvas) {
|
|
805
|
+
console.error('HDR preview canvas not found, cannot render preview');
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// If texture doesn't have image data, show error message
|
|
810
|
+
if (!texture || !texture.image) {
|
|
811
|
+
console.warn('No texture or image data found:', texture);
|
|
812
|
+
showNoImageMessage(canvas, noImageMessage, 'No image data available.');
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
console.log('Rendering environment texture preview as 3D sphere');
|
|
817
|
+
|
|
818
|
+
// Hide the no image message
|
|
819
|
+
if (noImageMessage) noImageMessage.style.display = 'none';
|
|
820
|
+
|
|
821
|
+
// Make canvas visible
|
|
822
|
+
canvas.style.display = 'block';
|
|
823
|
+
|
|
824
|
+
// Show background data info and hide no background message
|
|
825
|
+
toggleBackgroundMessages(true);
|
|
826
|
+
|
|
827
|
+
// Set canvas size (always square for the sphere preview)
|
|
828
|
+
const previewSize = externalCanvas ? Math.max(canvas.width, canvas.height) : 260;
|
|
829
|
+
|
|
830
|
+
// Always enforce a square aspect ratio regardless of container dimensions
|
|
831
|
+
canvas.width = previewSize;
|
|
832
|
+
canvas.height = previewSize;
|
|
833
|
+
|
|
834
|
+
// Apply class to ensure proper scaling with CSS
|
|
835
|
+
canvas.classList.add('square-preview');
|
|
836
|
+
|
|
837
|
+
try {
|
|
838
|
+
// Create a mini Three.js scene for the sphere preview
|
|
839
|
+
// Use the imported THREE directly
|
|
840
|
+
createSpherePreview(THREE, texture, canvas, noImageMessage);
|
|
841
|
+
|
|
842
|
+
return true;
|
|
843
|
+
} catch (error) {
|
|
844
|
+
console.error('Error rendering HDR preview as sphere:', error);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return true;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Create a 3D sphere preview with the environment texture
|
|
852
|
+
* @param {Object} THREE - The Three.js library
|
|
853
|
+
* @param {THREE.Texture} texture - The environment texture
|
|
854
|
+
* @param {HTMLCanvasElement} canvas - The canvas element
|
|
855
|
+
* @param {HTMLElement} noImageMessage - The no image message element
|
|
856
|
+
*/
|
|
857
|
+
export function createSpherePreview(THREE, texture, canvas, noImageMessage) {
|
|
858
|
+
try {
|
|
859
|
+
// Create a mini renderer
|
|
860
|
+
const renderer = new THREE.WebGLRenderer({
|
|
861
|
+
canvas: canvas,
|
|
862
|
+
alpha: true,
|
|
863
|
+
antialias: true
|
|
864
|
+
});
|
|
865
|
+
renderer.setSize(canvas.width, canvas.height);
|
|
866
|
+
renderer.setPixelRatio(window.devicePixelRatio);
|
|
867
|
+
|
|
868
|
+
// Critical for HDR/EXR: set proper encoding and tone mapping
|
|
869
|
+
// Update to use modern THREE.js properties
|
|
870
|
+
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
871
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
872
|
+
renderer.toneMappingExposure = 1.0;
|
|
873
|
+
|
|
874
|
+
// Create a mini scene
|
|
875
|
+
const scene = new THREE.Scene();
|
|
876
|
+
scene.background = new THREE.Color(0x111111); // Dark background
|
|
877
|
+
|
|
878
|
+
// Set the environment texture for the scene - this affects reflective materials
|
|
879
|
+
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
880
|
+
scene.environment = texture;
|
|
881
|
+
|
|
882
|
+
// Create a mini camera - move it back a bit more to make the sphere appear smaller
|
|
883
|
+
const camera = new THREE.PerspectiveCamera(40, 1, 0.1, 100);
|
|
884
|
+
camera.position.z = 3.2; // Increased camera distance to make sphere smaller
|
|
885
|
+
|
|
886
|
+
// Create a 3D sphere with high polygon count for smooth reflections
|
|
887
|
+
// Make the sphere slightly smaller
|
|
888
|
+
const sphereGeometry = new THREE.SphereGeometry(0.8, 64, 64);
|
|
889
|
+
|
|
890
|
+
// Create a metallic material
|
|
891
|
+
const metallicMaterial = new THREE.MeshStandardMaterial({
|
|
892
|
+
color: 0xffffff,
|
|
893
|
+
metalness: 1.0,
|
|
894
|
+
roughness: 0.05,
|
|
895
|
+
envMapIntensity: 1.0
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
// Create and add the metallic sphere
|
|
899
|
+
const sphere = new THREE.Mesh(sphereGeometry, metallicMaterial);
|
|
900
|
+
scene.add(sphere);
|
|
901
|
+
|
|
902
|
+
// Add some lighting - even with environment lighting, we need some direct light
|
|
903
|
+
// for better highlights
|
|
904
|
+
const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
|
|
905
|
+
scene.add(ambientLight);
|
|
906
|
+
|
|
907
|
+
// Add a directional light for highlights
|
|
908
|
+
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
|
|
909
|
+
directionalLight.position.set(1, 1, 1);
|
|
910
|
+
scene.add(directionalLight);
|
|
911
|
+
|
|
912
|
+
// Add a point light for additional dimension
|
|
913
|
+
const pointLight = new THREE.PointLight(0xffffff, 0.5);
|
|
914
|
+
pointLight.position.set(-1, 1, 0.5);
|
|
915
|
+
scene.add(pointLight);
|
|
916
|
+
|
|
917
|
+
let animationFrameId;
|
|
918
|
+
const clock = new THREE.Clock();
|
|
919
|
+
|
|
920
|
+
// Add a simple message to indicate the sphere is interactive
|
|
921
|
+
const interactiveHint = document.createElement('div');
|
|
922
|
+
interactiveHint.style.position = 'absolute';
|
|
923
|
+
interactiveHint.style.bottom = '5px';
|
|
924
|
+
interactiveHint.style.left = '50%';
|
|
925
|
+
interactiveHint.style.transform = 'translateX(-50%)';
|
|
926
|
+
interactiveHint.style.fontSize = '10px';
|
|
927
|
+
interactiveHint.style.color = 'rgba(255,255,255,0.7)';
|
|
928
|
+
interactiveHint.style.pointerEvents = 'none';
|
|
929
|
+
canvas.parentElement.style.position = 'relative';
|
|
930
|
+
canvas.parentElement.appendChild(interactiveHint);
|
|
931
|
+
|
|
932
|
+
// Initial slight rotation to show it's 3D
|
|
933
|
+
sphere.rotation.y = Math.PI / 6;
|
|
934
|
+
sphere.rotation.x = Math.PI / 12;
|
|
935
|
+
|
|
936
|
+
// Create dedicated OrbitControls for the preview sphere
|
|
937
|
+
// We need to dynamically import it for this specific use case
|
|
938
|
+
let previewControls = null;
|
|
939
|
+
let previewControlsReady = false;
|
|
940
|
+
|
|
941
|
+
// Function to dynamically import and create OrbitControls
|
|
942
|
+
const initPreviewControls = async () => {
|
|
943
|
+
try {
|
|
944
|
+
// Use dynamic import to get OrbitControls
|
|
945
|
+
const { OrbitControls } = await import('three/addons/controls/OrbitControls.js');
|
|
946
|
+
|
|
947
|
+
// Create dedicated controls for this preview only
|
|
948
|
+
previewControls = new OrbitControls(camera, canvas);
|
|
949
|
+
|
|
950
|
+
// Configure the preview controls
|
|
951
|
+
previewControls.enableDamping = true;
|
|
952
|
+
previewControls.dampingFactor = 0.05;
|
|
953
|
+
previewControls.rotateSpeed = 1.0;
|
|
954
|
+
previewControls.enableZoom = false;
|
|
955
|
+
previewControls.enablePan = false;
|
|
956
|
+
|
|
957
|
+
// Mark as ready
|
|
958
|
+
previewControlsReady = true;
|
|
959
|
+
|
|
960
|
+
console.log('Preview controls initialized successfully');
|
|
961
|
+
} catch (error) {
|
|
962
|
+
console.error('Failed to initialize preview controls:', error);
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
// Initialize the controls
|
|
967
|
+
initPreviewControls();
|
|
968
|
+
|
|
969
|
+
function renderSphere() {
|
|
970
|
+
animationFrameId = requestAnimationFrame(renderSphere);
|
|
971
|
+
|
|
972
|
+
const delta = clock.getDelta();
|
|
973
|
+
|
|
974
|
+
// Update preview controls if available
|
|
975
|
+
if (previewControlsReady && previewControls) {
|
|
976
|
+
previewControls.update();
|
|
977
|
+
} else {
|
|
978
|
+
// If no controls yet, add a very slow rotation to show it's 3D
|
|
979
|
+
sphere.rotation.y += delta * 0.1;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
renderer.render(scene, camera);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// Start the animation
|
|
986
|
+
renderSphere();
|
|
987
|
+
|
|
988
|
+
// Store the animation frame ID for cleanup
|
|
989
|
+
canvas.setAttribute('data-animation-id', animationFrameId);
|
|
990
|
+
|
|
991
|
+
// Add cleanup function when tab changes or element is removed
|
|
992
|
+
const cleanup = () => {
|
|
993
|
+
if (animationFrameId) {
|
|
994
|
+
cancelAnimationFrame(animationFrameId);
|
|
995
|
+
animationFrameId = null;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Remove the interactive hint
|
|
999
|
+
if (interactiveHint && interactiveHint.parentElement) {
|
|
1000
|
+
interactiveHint.parentElement.removeChild(interactiveHint);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Dispose of the preview controls if they exist
|
|
1004
|
+
if (previewControls) {
|
|
1005
|
+
previewControls.dispose();
|
|
1006
|
+
previewControls = null;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Proper disposal of Three.js resources
|
|
1010
|
+
renderer.dispose();
|
|
1011
|
+
sphereGeometry.dispose();
|
|
1012
|
+
metallicMaterial.dispose();
|
|
1013
|
+
|
|
1014
|
+
// Remove references
|
|
1015
|
+
sphere.geometry = null;
|
|
1016
|
+
sphere.material = null;
|
|
1017
|
+
scene.remove(sphere);
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
// Store cleanup function for later use
|
|
1021
|
+
canvas.cleanup = cleanup;
|
|
1022
|
+
console.log('Successfully rendered environment map as interactive 3D sphere');
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
console.error('Error in createSpherePreview:', error);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Show "No image data" message
|
|
1030
|
+
* @param {HTMLCanvasElement} canvas - The canvas element
|
|
1031
|
+
* @param {HTMLElement} messageEl - The message element to show
|
|
1032
|
+
* @param {string} message - The error message to display
|
|
1033
|
+
*/
|
|
1034
|
+
export function showNoImageMessage(canvas, messageEl, message = 'No image data available.') {
|
|
1035
|
+
// Hide canvas
|
|
1036
|
+
if (canvas) {
|
|
1037
|
+
canvas.style.display = 'none';
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Set error message if provided
|
|
1041
|
+
if (messageEl) {
|
|
1042
|
+
messageEl.textContent = message;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Use the standardized toggle function to show/hide appropriate messages
|
|
1046
|
+
toggleLightingMessages(false);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Clear the lighting info display
|
|
1051
|
+
*/
|
|
1052
|
+
function clearLightingInfo() {
|
|
1053
|
+
// Clear the stored metadata
|
|
1054
|
+
currentLightingMetadata = null;
|
|
1055
|
+
environmentTexture = null;
|
|
1056
|
+
|
|
1057
|
+
// Find the UI elements
|
|
1058
|
+
const filenameEl = document.getElementById('lighting-filename');
|
|
1059
|
+
const typeEl = document.getElementById('lighting-type');
|
|
1060
|
+
const resolutionEl = document.getElementById('lighting-resolution');
|
|
1061
|
+
const sizeEl = document.getElementById('lighting-size');
|
|
1062
|
+
const rangeEl = document.getElementById('lighting-range');
|
|
1063
|
+
const luminanceEl = document.getElementById('lighting-luminance');
|
|
1064
|
+
const softwareEl = document.getElementById('lighting-software');
|
|
1065
|
+
|
|
1066
|
+
// Reset all values
|
|
1067
|
+
if (filenameEl) filenameEl.textContent = '-';
|
|
1068
|
+
if (typeEl) typeEl.textContent = '-';
|
|
1069
|
+
if (resolutionEl) resolutionEl.textContent = '-';
|
|
1070
|
+
if (sizeEl) sizeEl.textContent = '-';
|
|
1071
|
+
if (rangeEl) rangeEl.textContent = '-';
|
|
1072
|
+
if (luminanceEl) luminanceEl.textContent = '-';
|
|
1073
|
+
if (softwareEl) softwareEl.textContent = '-';
|
|
1074
|
+
|
|
1075
|
+
// Hide lighting info and show no data message
|
|
1076
|
+
toggleLightingMessages(false);
|
|
1077
|
+
|
|
1078
|
+
// Hide the HDR/EXR radio option since we no longer have environment lighting
|
|
1079
|
+
toggleOptionVisibility('hdr-option', false);
|
|
1080
|
+
console.log('Hiding HDR/EXR radio option');
|
|
1081
|
+
|
|
1082
|
+
// Hide background info and show no background message
|
|
1083
|
+
toggleBackgroundMessages(false);
|
|
1084
|
+
|
|
1085
|
+
// Reset collapse state
|
|
1086
|
+
const metadataContents = document.querySelectorAll('.metadata-content');
|
|
1087
|
+
if (metadataContents) {
|
|
1088
|
+
metadataContents.forEach(content => {
|
|
1089
|
+
content.style.display = 'none';
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const indicators = document.querySelectorAll('.collapse-indicator');
|
|
1094
|
+
if (indicators) {
|
|
1095
|
+
indicators.forEach(indicator => {
|
|
1096
|
+
indicator.textContent = '[+]';
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Update slider visibility - we have no HDR/EXR data
|
|
1101
|
+
updateSliderVisibility(false);
|
|
1102
|
+
|
|
1103
|
+
// Clean up any ThreeJS resources
|
|
1104
|
+
const canvas = document.getElementById('hdr-preview-canvas');
|
|
1105
|
+
if (canvas) {
|
|
1106
|
+
// Execute cleanup function if it exists
|
|
1107
|
+
if (typeof canvas.cleanup === 'function') {
|
|
1108
|
+
canvas.cleanup();
|
|
1109
|
+
canvas.cleanup = null;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// Cancel any animation frame
|
|
1113
|
+
const animationId = canvas.getAttribute('data-animation-id');
|
|
1114
|
+
if (animationId) {
|
|
1115
|
+
cancelAnimationFrame(parseInt(animationId, 10));
|
|
1116
|
+
canvas.removeAttribute('data-animation-id');
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// Clear the canvas
|
|
1120
|
+
const ctx = canvas.getContext('2d');
|
|
1121
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1122
|
+
canvas.style.display = 'none';
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* Update the background info display with metadata
|
|
1128
|
+
* @param {Object} metadata - The background image metadata
|
|
1129
|
+
* @param {boolean} [skipRendering=false] - Whether to skip rendering the preview
|
|
1130
|
+
*/
|
|
1131
|
+
export function updateBackgroundInfo(metadata, skipRendering = false) {
|
|
1132
|
+
console.log('Updating background info with metadata:', metadata.fileName, metadata.type);
|
|
1133
|
+
|
|
1134
|
+
// Store the metadata for later use if the panel isn't ready yet
|
|
1135
|
+
currentBackgroundMetadata = metadata;
|
|
1136
|
+
|
|
1137
|
+
// Try to initialize the panel if not already done
|
|
1138
|
+
if (!controlsInitialized) {
|
|
1139
|
+
console.warn('Panel not initialized yet, storing metadata for later');
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Find the UI elements
|
|
1144
|
+
const filenameEl = document.getElementById('bg-filename');
|
|
1145
|
+
const typeEl = document.getElementById('bg-type');
|
|
1146
|
+
const resolutionEl = document.getElementById('bg-resolution');
|
|
1147
|
+
const sizeEl = document.getElementById('bg-size');
|
|
1148
|
+
|
|
1149
|
+
// Make sure all elements exist
|
|
1150
|
+
if (!filenameEl || !typeEl || !resolutionEl || !sizeEl) {
|
|
1151
|
+
console.error('Cannot update background info: UI elements not found');
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// Update the UI with metadata
|
|
1156
|
+
filenameEl.textContent = metadata.fileName || '-';
|
|
1157
|
+
typeEl.textContent = metadata.type || '-';
|
|
1158
|
+
|
|
1159
|
+
const width = metadata.dimensions?.width || 0;
|
|
1160
|
+
const height = metadata.dimensions?.height || 0;
|
|
1161
|
+
resolutionEl.textContent = (width && height) ? `${width} × ${height}` : '-';
|
|
1162
|
+
|
|
1163
|
+
const fileSizeMB = metadata.fileSizeBytes ? (metadata.fileSizeBytes / 1024 / 1024).toFixed(2) + ' MB' : '-';
|
|
1164
|
+
sizeEl.textContent = fileSizeMB;
|
|
1165
|
+
|
|
1166
|
+
// Make the background option visible since we have a background image
|
|
1167
|
+
toggleOptionVisibility('background-option', true);
|
|
1168
|
+
console.log('Making Background Image radio option visible');
|
|
1169
|
+
|
|
1170
|
+
// Respect the current radio selection without changing it on automatic background updates
|
|
1171
|
+
// Only auto-select the option if this is a fresh load (no option selected yet)
|
|
1172
|
+
if (currentBackgroundOption === null || currentBackgroundOption === undefined) {
|
|
1173
|
+
// First-time initialization - default to 'none' but don't change scene background
|
|
1174
|
+
const noneRadio = document.querySelector('input[name="bg-option"][value="none"]');
|
|
1175
|
+
if (noneRadio) {
|
|
1176
|
+
noneRadio.checked = true;
|
|
1177
|
+
currentBackgroundOption = 'none';
|
|
1178
|
+
}
|
|
1179
|
+
} else {
|
|
1180
|
+
// Make sure the correct radio button matches our stored selection
|
|
1181
|
+
const selectedRadio = document.querySelector(`input[name="bg-option"][value="${currentBackgroundOption}"]`);
|
|
1182
|
+
if (selectedRadio) {
|
|
1183
|
+
selectedRadio.checked = true;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// Make sure any collapsible content is still properly collapsed
|
|
1188
|
+
const metadataContents = document.querySelectorAll('.metadata-content');
|
|
1189
|
+
if (metadataContents && metadataContents.length > 0) {
|
|
1190
|
+
console.log('Ensuring collapsible content is collapsed initially');
|
|
1191
|
+
metadataContents.forEach(content => {
|
|
1192
|
+
if (!content.classList.contains('active')) {
|
|
1193
|
+
content.style.display = 'none';
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
// Make sure any indicators show the right symbol
|
|
1198
|
+
const indicators = document.querySelectorAll('.collapse-indicator');
|
|
1199
|
+
if (indicators && indicators.length > 0) {
|
|
1200
|
+
indicators.forEach(indicator => {
|
|
1201
|
+
// Always set to '+' to indicate collapsed state
|
|
1202
|
+
indicator.textContent = '[+]';
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Only render preview if not skipped and we have a file in state
|
|
1208
|
+
if (!skipRendering) {
|
|
1209
|
+
const state = getState();
|
|
1210
|
+
if (state.backgroundFile && !backgroundTexture) {
|
|
1211
|
+
console.log('Found background file in state, rendering preview');
|
|
1212
|
+
// Create a preview of the background image
|
|
1213
|
+
renderBackgroundPreview(state.backgroundFile);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Render the background image preview on canvas
|
|
1220
|
+
* @param {File|Blob|THREE.Texture} fileOrTexture - The background file or texture to render
|
|
1221
|
+
* @returns {boolean} - Whether preview was rendered successfully
|
|
1222
|
+
*/
|
|
1223
|
+
export function renderBackgroundPreview(fileOrTexture) {
|
|
1224
|
+
// Simple prevention of recursive calls
|
|
1225
|
+
if (isLoadingEnvironmentFile) {
|
|
1226
|
+
console.log('Already loading an HDR/EXR file, preventing recursive calls');
|
|
1227
|
+
return true;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// Look for the canvas element
|
|
1231
|
+
const canvas = document.getElementById('bg-preview-canvas');
|
|
1232
|
+
const noImageMessage = document.getElementById('no-bg-image-message');
|
|
1233
|
+
|
|
1234
|
+
// If canvas not found, panel may not be initialized yet
|
|
1235
|
+
if (!canvas) {
|
|
1236
|
+
console.error('Background preview canvas not found, cannot render preview');
|
|
1237
|
+
return false;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// Store the texture for later use
|
|
1241
|
+
backgroundTexture = fileOrTexture;
|
|
1242
|
+
|
|
1243
|
+
// If there's no file or texture, show error message
|
|
1244
|
+
if (!fileOrTexture) {
|
|
1245
|
+
console.warn('No background file or texture provided');
|
|
1246
|
+
showNoBackgroundImageMessage(canvas, noImageMessage, 'No image data available.');
|
|
1247
|
+
return false;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
console.log('Rendering background image preview for:',
|
|
1251
|
+
typeof fileOrTexture === 'object' && fileOrTexture.isTexture
|
|
1252
|
+
? 'Texture object'
|
|
1253
|
+
: (fileOrTexture.name || 'Unknown file'));
|
|
1254
|
+
|
|
1255
|
+
// Hide the no image message
|
|
1256
|
+
if (noImageMessage) noImageMessage.style.display = 'none';
|
|
1257
|
+
|
|
1258
|
+
// Make canvas visible
|
|
1259
|
+
canvas.style.display = 'block';
|
|
1260
|
+
|
|
1261
|
+
// Show background data info and hide no background message
|
|
1262
|
+
toggleBackgroundMessages(true);
|
|
1263
|
+
|
|
1264
|
+
try {
|
|
1265
|
+
// Different handling based on what type of data we have
|
|
1266
|
+
if (fileOrTexture instanceof File || fileOrTexture instanceof Blob) {
|
|
1267
|
+
const fileName = fileOrTexture.name ? fileOrTexture.name.toLowerCase() : '';
|
|
1268
|
+
const isEXR = fileName.endsWith('.exr');
|
|
1269
|
+
const isHDR = fileName.endsWith('.hdr');
|
|
1270
|
+
|
|
1271
|
+
// Special handling for EXR/HDR files
|
|
1272
|
+
if (isEXR || isHDR) {
|
|
1273
|
+
console.log(`Handling ${isEXR ? 'EXR' : 'HDR'} file with special loader`);
|
|
1274
|
+
|
|
1275
|
+
// Set loading flag to prevent recursion
|
|
1276
|
+
isLoadingEnvironmentFile = true;
|
|
1277
|
+
|
|
1278
|
+
// Create metadata early for better UX
|
|
1279
|
+
if (!currentBackgroundMetadata) {
|
|
1280
|
+
currentBackgroundMetadata = {
|
|
1281
|
+
fileName: fileOrTexture.name,
|
|
1282
|
+
type: isEXR ? 'EXR' : 'HDR',
|
|
1283
|
+
dimensions: { width: 0, height: 0 }, // Will be updated when loaded
|
|
1284
|
+
fileSizeBytes: fileOrTexture.size
|
|
1285
|
+
};
|
|
1286
|
+
updateBackgroundInfo(currentBackgroundMetadata);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// Set canvas to square for sphere preview
|
|
1290
|
+
const previewSize = 260;
|
|
1291
|
+
canvas.width = previewSize;
|
|
1292
|
+
canvas.height = previewSize;
|
|
1293
|
+
|
|
1294
|
+
// Load the file with the appropriate loader
|
|
1295
|
+
const loadHDRorEXR = async () => {
|
|
1296
|
+
try {
|
|
1297
|
+
// Use the statically imported THREE
|
|
1298
|
+
// Choose the right loader based on file type
|
|
1299
|
+
let loader;
|
|
1300
|
+
if (isEXR) {
|
|
1301
|
+
const { EXRLoader } = await import('three/addons/loaders/EXRLoader.js');
|
|
1302
|
+
loader = new EXRLoader();
|
|
1303
|
+
} else {
|
|
1304
|
+
const { RGBELoader } = await import('three/addons/loaders/RGBELoader.js');
|
|
1305
|
+
loader = new RGBELoader();
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// Create a file URL
|
|
1309
|
+
const url = URL.createObjectURL(fileOrTexture);
|
|
1310
|
+
|
|
1311
|
+
// Load the texture
|
|
1312
|
+
loader.load(url,
|
|
1313
|
+
// onLoad callback
|
|
1314
|
+
(texture) => {
|
|
1315
|
+
// Update metadata with actual dimensions
|
|
1316
|
+
if (currentBackgroundMetadata) {
|
|
1317
|
+
currentBackgroundMetadata.dimensions = {
|
|
1318
|
+
width: texture.image.width,
|
|
1319
|
+
height: texture.image.height
|
|
1320
|
+
};
|
|
1321
|
+
// Important: skip updateBackgroundInfo's render step
|
|
1322
|
+
// to prevent recursive loading
|
|
1323
|
+
// Just update UI fields directly
|
|
1324
|
+
const widthHeight = `${texture.image.width} × ${texture.image.height}`;
|
|
1325
|
+
const resolutionEl = document.getElementById('bg-resolution');
|
|
1326
|
+
if (resolutionEl) {
|
|
1327
|
+
resolutionEl.textContent = widthHeight;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// Create sphere preview with the texture
|
|
1332
|
+
createSpherePreview(THREE, texture, canvas, noImageMessage);
|
|
1333
|
+
|
|
1334
|
+
// Clean up URL object
|
|
1335
|
+
URL.revokeObjectURL(url);
|
|
1336
|
+
|
|
1337
|
+
// Clear the loading flag
|
|
1338
|
+
isLoadingEnvironmentFile = false;
|
|
1339
|
+
},
|
|
1340
|
+
// onProgress callback
|
|
1341
|
+
(xhr) => {
|
|
1342
|
+
const percentComplete = xhr.loaded / xhr.total * 100;
|
|
1343
|
+
console.log(`${isEXR ? 'EXR' : 'HDR'} loading: ${Math.round(percentComplete)}%`);
|
|
1344
|
+
},
|
|
1345
|
+
// onError callback
|
|
1346
|
+
(error) => {
|
|
1347
|
+
console.error(`Error loading ${isEXR ? 'EXR' : 'HDR'} file:`, error);
|
|
1348
|
+
showNoBackgroundImageMessage(canvas, noImageMessage, `Error loading ${isEXR ? 'EXR' : 'HDR'} file`);
|
|
1349
|
+
URL.revokeObjectURL(url);
|
|
1350
|
+
|
|
1351
|
+
// Clear the loading flag on error
|
|
1352
|
+
isLoadingEnvironmentFile = false;
|
|
1353
|
+
}
|
|
1354
|
+
);
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
console.error(`Error in ${isEXR ? 'EXR' : 'HDR'} loading:`, error);
|
|
1357
|
+
showNoBackgroundImageMessage(canvas, noImageMessage, `Error: Could not load ${isEXR ? 'EXR' : 'HDR'} file`);
|
|
1358
|
+
|
|
1359
|
+
// Clear the loading flag on error
|
|
1360
|
+
isLoadingEnvironmentFile = false;
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
// Start loading
|
|
1365
|
+
loadHDRorEXR();
|
|
1366
|
+
return true;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// For standard image files, create a texture and use 3D sphere preview
|
|
1370
|
+
const url = URL.createObjectURL(fileOrTexture);
|
|
1371
|
+
const img = new Image();
|
|
1372
|
+
|
|
1373
|
+
img.onload = function() {
|
|
1374
|
+
// Get file dimensions
|
|
1375
|
+
const width = img.width;
|
|
1376
|
+
const height = img.height;
|
|
1377
|
+
|
|
1378
|
+
// Update metadata if needed
|
|
1379
|
+
if (!currentBackgroundMetadata) {
|
|
1380
|
+
currentBackgroundMetadata = {
|
|
1381
|
+
fileName: fileOrTexture.name,
|
|
1382
|
+
type: fileOrTexture.type || 'Image',
|
|
1383
|
+
dimensions: { width, height },
|
|
1384
|
+
fileSizeBytes: fileOrTexture.size
|
|
1385
|
+
};
|
|
1386
|
+
updateBackgroundInfo(currentBackgroundMetadata);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// Set canvas size (square for the sphere preview)
|
|
1390
|
+
const previewSize = 260;
|
|
1391
|
+
canvas.width = previewSize;
|
|
1392
|
+
canvas.height = previewSize;
|
|
1393
|
+
|
|
1394
|
+
// Create texture from loaded image and create sphere preview
|
|
1395
|
+
try {
|
|
1396
|
+
// Use the statically imported THREE
|
|
1397
|
+
const textureLoader = new THREE.TextureLoader();
|
|
1398
|
+
const texture = textureLoader.load(url);
|
|
1399
|
+
|
|
1400
|
+
// Use the same sphere preview as for environment maps
|
|
1401
|
+
createSpherePreview(THREE, texture, canvas, noImageMessage);
|
|
1402
|
+
|
|
1403
|
+
// Clean up URL object after texture is loaded
|
|
1404
|
+
URL.revokeObjectURL(url);
|
|
1405
|
+
} catch (error) {
|
|
1406
|
+
console.error('Error creating sphere preview:', error);
|
|
1407
|
+
|
|
1408
|
+
// Fall back to 2D canvas if sphere preview fails
|
|
1409
|
+
const ctx = canvas.getContext('2d');
|
|
1410
|
+
canvas.width = width;
|
|
1411
|
+
canvas.height = height;
|
|
1412
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
1413
|
+
URL.revokeObjectURL(url);
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
|
|
1417
|
+
img.onerror = function() {
|
|
1418
|
+
console.error('Error loading background image');
|
|
1419
|
+
showNoBackgroundImageMessage(canvas, noImageMessage, 'Error loading image');
|
|
1420
|
+
URL.revokeObjectURL(url);
|
|
1421
|
+
};
|
|
1422
|
+
|
|
1423
|
+
img.src = url;
|
|
1424
|
+
return true;
|
|
1425
|
+
} else if (typeof fileOrTexture === 'object' && fileOrTexture.isTexture) {
|
|
1426
|
+
// For THREE.Texture objects, use sphere preview directly
|
|
1427
|
+
|
|
1428
|
+
// Create metadata if not present
|
|
1429
|
+
if (!currentBackgroundMetadata) {
|
|
1430
|
+
currentBackgroundMetadata = {
|
|
1431
|
+
fileName: fileOrTexture.userData?.fileName || 'Background Texture',
|
|
1432
|
+
type: fileOrTexture.isHDRTexture ? 'HDR Texture' : 'Standard Texture',
|
|
1433
|
+
dimensions: {
|
|
1434
|
+
width: fileOrTexture.image ? fileOrTexture.image.width : 0,
|
|
1435
|
+
height: fileOrTexture.image ? fileOrTexture.image.height : 0
|
|
1436
|
+
},
|
|
1437
|
+
fileSizeBytes: 0 // Unknown size
|
|
1438
|
+
};
|
|
1439
|
+
updateBackgroundInfo(currentBackgroundMetadata);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// Set canvas size (square for the sphere preview)
|
|
1443
|
+
const previewSize = 260;
|
|
1444
|
+
canvas.width = previewSize;
|
|
1445
|
+
canvas.height = previewSize;
|
|
1446
|
+
|
|
1447
|
+
try {
|
|
1448
|
+
// Use the statically imported THREE
|
|
1449
|
+
createSpherePreview(THREE, fileOrTexture, canvas, noImageMessage);
|
|
1450
|
+
return true;
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
console.error('Error creating sphere preview for texture:', error);
|
|
1453
|
+
}
|
|
1454
|
+
return true;
|
|
1455
|
+
} else {
|
|
1456
|
+
console.error('Unknown fileOrTexture type:', fileOrTexture);
|
|
1457
|
+
showNoBackgroundImageMessage(canvas, noImageMessage, 'Unsupported background format.');
|
|
1458
|
+
return false;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
} catch (error) {
|
|
1462
|
+
console.error('Error in renderBackgroundPreview:', error);
|
|
1463
|
+
showNoBackgroundImageMessage(canvas, noImageMessage, `Error: ${error.message}`);
|
|
1464
|
+
return false;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* Show "No background image data" message
|
|
1470
|
+
* @param {HTMLCanvasElement} canvas - The canvas element
|
|
1471
|
+
* @param {HTMLElement} messageEl - The message element to show
|
|
1472
|
+
* @param {string} message - The error message to display
|
|
1473
|
+
*/
|
|
1474
|
+
export function showNoBackgroundImageMessage(canvas, messageEl, message = 'No image data available.') {
|
|
1475
|
+
// Hide canvas
|
|
1476
|
+
if (canvas) {
|
|
1477
|
+
canvas.style.display = 'none';
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
// Set error message if provided
|
|
1481
|
+
if (messageEl) {
|
|
1482
|
+
messageEl.textContent = message;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
// Use the standardized toggle function to show/hide appropriate messages
|
|
1486
|
+
toggleBackgroundMessages(false);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/**
|
|
1490
|
+
* Update the World panel with current state
|
|
1491
|
+
*/
|
|
1492
|
+
export function updateWorldPanel() {
|
|
1493
|
+
// If still not initialized, log error and return
|
|
1494
|
+
if (!controlsInitialized) {
|
|
1495
|
+
console.error('Cannot update World panel: not initialized');
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
const state = getState();
|
|
1500
|
+
|
|
1501
|
+
// Update lighting controls with current values
|
|
1502
|
+
const ambientIntensityControl = document.getElementById('ambient-light-intensity');
|
|
1503
|
+
const directionalIntensityControl = document.getElementById('directional-light-intensity');
|
|
1504
|
+
const exposureControl = document.getElementById('exposure-value');
|
|
1505
|
+
|
|
1506
|
+
if (state.ambientLight && ambientIntensityControl) {
|
|
1507
|
+
ambientIntensityControl.value = state.ambientLight.intensity;
|
|
1508
|
+
const valueDisplay = ambientIntensityControl.previousElementSibling.querySelector('.value-display');
|
|
1509
|
+
if (valueDisplay) {
|
|
1510
|
+
valueDisplay.textContent = state.ambientLight.intensity.toFixed(1);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
if (state.directionalLight && directionalIntensityControl) {
|
|
1515
|
+
directionalIntensityControl.value = state.directionalLight.intensity;
|
|
1516
|
+
const valueDisplay = directionalIntensityControl.previousElementSibling.querySelector('.value-display');
|
|
1517
|
+
if (valueDisplay) {
|
|
1518
|
+
valueDisplay.textContent = state.directionalLight.intensity.toFixed(1);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
if (state.renderer && exposureControl) {
|
|
1523
|
+
exposureControl.value = state.renderer.toneMappingExposure || 1.0;
|
|
1524
|
+
const valueDisplay = exposureControl.previousElementSibling.querySelector('.value-display');
|
|
1525
|
+
if (valueDisplay) {
|
|
1526
|
+
valueDisplay.textContent = (state.renderer.toneMappingExposure || 1.0).toFixed(1);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// Update lighting message visibility
|
|
1531
|
+
updateLightingMessage();
|
|
1532
|
+
|
|
1533
|
+
// Get the current state for file existence - be more thorough with checks
|
|
1534
|
+
const hasEnvironmentTexture = state.lightingFile !== null ||
|
|
1535
|
+
(state.scene && state.scene.environment) ||
|
|
1536
|
+
currentLightingMetadata !== null;
|
|
1537
|
+
|
|
1538
|
+
const hasBackgroundTexture = state.backgroundTexture !== null ||
|
|
1539
|
+
state.backgroundFile !== null ||
|
|
1540
|
+
currentBackgroundMetadata !== null;
|
|
1541
|
+
|
|
1542
|
+
// Log the current state for debugging
|
|
1543
|
+
console.log('Updating world panel with state:', {
|
|
1544
|
+
hasEnvironmentTexture,
|
|
1545
|
+
hasBackgroundTexture,
|
|
1546
|
+
lightingFile: state.lightingFile ? state.lightingFile.name : null,
|
|
1547
|
+
backgroundFile: state.backgroundFile ? state.backgroundFile.name : null,
|
|
1548
|
+
environment: state.scene && state.scene.environment ? 'present' : null,
|
|
1549
|
+
backgroundTexture: state.backgroundTexture ? 'present' : null,
|
|
1550
|
+
metadata: {
|
|
1551
|
+
lighting: currentLightingMetadata ? 'present' : null,
|
|
1552
|
+
background: currentBackgroundMetadata ? 'present' : null
|
|
1553
|
+
}
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1556
|
+
// Always update radio button visibility based on current state
|
|
1557
|
+
toggleOptionVisibility('hdr-option', hasEnvironmentTexture);
|
|
1558
|
+
toggleOptionVisibility('background-option', hasBackgroundTexture);
|
|
1559
|
+
|
|
1560
|
+
console.log(`Radio button visibility updated - HDR: ${hasEnvironmentTexture}, Background: ${hasBackgroundTexture}`);
|
|
1561
|
+
|
|
1562
|
+
// First render any available previews
|
|
1563
|
+
|
|
1564
|
+
// If we have an environment texture, render its preview
|
|
1565
|
+
if (state.scene && state.scene.environment && !environmentTexture) {
|
|
1566
|
+
environmentTexture = state.scene.environment;
|
|
1567
|
+
renderEnvironmentPreview(environmentTexture);
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// If we have a background file, render its preview
|
|
1571
|
+
if (state.backgroundFile && !backgroundTexture) {
|
|
1572
|
+
backgroundTexture = state.backgroundFile;
|
|
1573
|
+
renderBackgroundPreview(backgroundTexture);
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// Update background UI with our centralized system
|
|
1577
|
+
refreshBackgroundUI();
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
/**
|
|
1581
|
+
* Export a debug function to test EXR rendering directly
|
|
1582
|
+
* This can be called from the console for testing
|
|
1583
|
+
*/
|
|
1584
|
+
export function testRenderExr(file) {
|
|
1585
|
+
console.log('Manual EXR rendering test with file:', file);
|
|
1586
|
+
|
|
1587
|
+
if (!file) {
|
|
1588
|
+
console.error('No file provided');
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
// Import the EXRLoader
|
|
1593
|
+
import('three/addons/loaders/EXRLoader.js').then(({ EXRLoader }) => {
|
|
1594
|
+
console.log('EXRLoader imported successfully');
|
|
1595
|
+
|
|
1596
|
+
const loader = new EXRLoader();
|
|
1597
|
+
loader.setDataType(THREE.FloatType);
|
|
1598
|
+
|
|
1599
|
+
// Create URL
|
|
1600
|
+
const url = URL.createObjectURL(file);
|
|
1601
|
+
console.log('Created URL for manual test:', url);
|
|
1602
|
+
|
|
1603
|
+
// Load the texture
|
|
1604
|
+
loader.load(url, (texture) => {
|
|
1605
|
+
console.log('EXR loaded for manual test:', texture);
|
|
1606
|
+
console.log('EXR image data:', texture.image);
|
|
1607
|
+
|
|
1608
|
+
// Render it
|
|
1609
|
+
renderEnvironmentPreview(texture);
|
|
1610
|
+
|
|
1611
|
+
// Clean up
|
|
1612
|
+
URL.revokeObjectURL(url);
|
|
1613
|
+
},
|
|
1614
|
+
// Progress
|
|
1615
|
+
(xhr) => {
|
|
1616
|
+
if (xhr.lengthComputable) {
|
|
1617
|
+
const percentComplete = xhr.loaded / xhr.total * 100;
|
|
1618
|
+
console.log(`Manual test loading: ${Math.round(percentComplete)}%`);
|
|
1619
|
+
}
|
|
1620
|
+
},
|
|
1621
|
+
// Error
|
|
1622
|
+
(error) => {
|
|
1623
|
+
console.error('Error in manual test:', error);
|
|
1624
|
+
});
|
|
1625
|
+
}).catch(err => {
|
|
1626
|
+
console.error('Error importing EXRLoader for manual test:', err);
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
// Make the test function available globally for debugging
|
|
1631
|
+
window.testRenderExr = testRenderExr;
|
|
1632
|
+
|
|
1633
|
+
/**
|
|
1634
|
+
* Generate a preview for HDR/EXR files without affecting the world panel
|
|
1635
|
+
* This is specifically for the drop zone preview
|
|
1636
|
+
* @param {File} file - The HDR/EXR file
|
|
1637
|
+
* @param {HTMLElement} previewElement - The element to show the preview in
|
|
1638
|
+
*/
|
|
1639
|
+
export function generatePreviewOnly(file, previewElement) {
|
|
1640
|
+
if (!file || !previewElement) return;
|
|
1641
|
+
|
|
1642
|
+
// Check if we've already generated a texture for this file
|
|
1643
|
+
const cachedTexture = backgroundTextureCache.get(file.name);
|
|
1644
|
+
if (cachedTexture) {
|
|
1645
|
+
console.log('Using cached texture for preview:', file.name);
|
|
1646
|
+
displayCachedPreview(cachedTexture, previewElement);
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
console.log('Generating standalone preview for:', file.name);
|
|
1651
|
+
|
|
1652
|
+
// Create a canvas for the preview
|
|
1653
|
+
const canvas = document.createElement('canvas');
|
|
1654
|
+
canvas.width = 100; // Small size for the dropzone preview
|
|
1655
|
+
canvas.height = 100;
|
|
1656
|
+
|
|
1657
|
+
// Use appropriate loader based on file extension
|
|
1658
|
+
const extension = file.name.split('.').pop().toLowerCase();
|
|
1659
|
+
const isEXR = extension === 'exr';
|
|
1660
|
+
const isHDR = extension === 'hdr';
|
|
1661
|
+
|
|
1662
|
+
// Load and display the preview
|
|
1663
|
+
Promise.resolve().then(async () => {
|
|
1664
|
+
try {
|
|
1665
|
+
// Create a URL for the file
|
|
1666
|
+
const url = URL.createObjectURL(file);
|
|
1667
|
+
|
|
1668
|
+
// Select the appropriate loader
|
|
1669
|
+
let loader;
|
|
1670
|
+
if (isEXR) {
|
|
1671
|
+
const { EXRLoader } = await import('three/addons/loaders/EXRLoader.js');
|
|
1672
|
+
loader = new EXRLoader();
|
|
1673
|
+
} else {
|
|
1674
|
+
const { RGBELoader } = await import('three/addons/loaders/RGBELoader.js');
|
|
1675
|
+
loader = new RGBELoader();
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// Load the texture
|
|
1679
|
+
loader.load(
|
|
1680
|
+
url,
|
|
1681
|
+
(texture) => {
|
|
1682
|
+
// Store texture in the cache for future use
|
|
1683
|
+
texture.userData = { fileName: file.name };
|
|
1684
|
+
backgroundTextureCache.set(file.name, texture);
|
|
1685
|
+
|
|
1686
|
+
// Also store in state for use with debugging
|
|
1687
|
+
import('../../util/state/scene-state.js').then(stateModule => {
|
|
1688
|
+
stateModule.updateState({
|
|
1689
|
+
backgroundTexture: texture
|
|
1690
|
+
});
|
|
1691
|
+
});
|
|
1692
|
+
|
|
1693
|
+
// Clear the preview element and display the preview
|
|
1694
|
+
while (previewElement.firstChild) {
|
|
1695
|
+
previewElement.removeChild(previewElement.firstChild);
|
|
1696
|
+
}
|
|
1697
|
+
previewElement.textContent = '';
|
|
1698
|
+
previewElement.appendChild(canvas);
|
|
1699
|
+
|
|
1700
|
+
// Create a mini 3D preview
|
|
1701
|
+
createSimplePreview(THREE, texture, canvas);
|
|
1702
|
+
|
|
1703
|
+
// Clean up
|
|
1704
|
+
URL.revokeObjectURL(url);
|
|
1705
|
+
},
|
|
1706
|
+
(xhr) => {
|
|
1707
|
+
// Update progress
|
|
1708
|
+
if (xhr.lengthComputable) {
|
|
1709
|
+
const percentComplete = xhr.loaded / xhr.total * 100;
|
|
1710
|
+
previewElement.textContent = `Loading: ${Math.round(percentComplete)}%`;
|
|
1711
|
+
}
|
|
1712
|
+
},
|
|
1713
|
+
(error) => {
|
|
1714
|
+
console.error('Error loading preview:', error);
|
|
1715
|
+
previewElement.textContent = 'Preview unavailable';
|
|
1716
|
+
URL.revokeObjectURL(url);
|
|
1717
|
+
}
|
|
1718
|
+
);
|
|
1719
|
+
} catch (error) {
|
|
1720
|
+
console.error('Error generating preview:', error);
|
|
1721
|
+
previewElement.textContent = 'Preview unavailable';
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
/**
|
|
1727
|
+
* Display a cached texture in the preview element
|
|
1728
|
+
* @param {THREE.Texture} texture - The cached texture
|
|
1729
|
+
* @param {HTMLElement} previewElement - The element to display in
|
|
1730
|
+
*/
|
|
1731
|
+
function displayCachedPreview(texture, previewElement) {
|
|
1732
|
+
// Clear the preview element
|
|
1733
|
+
while (previewElement.firstChild) {
|
|
1734
|
+
previewElement.removeChild(previewElement.firstChild);
|
|
1735
|
+
}
|
|
1736
|
+
previewElement.textContent = '';
|
|
1737
|
+
|
|
1738
|
+
// Create a canvas for the preview
|
|
1739
|
+
const canvas = document.createElement('canvas');
|
|
1740
|
+
canvas.width = 100; // Small size for the dropzone preview
|
|
1741
|
+
canvas.height = 100;
|
|
1742
|
+
previewElement.appendChild(canvas);
|
|
1743
|
+
|
|
1744
|
+
// Create a mini 3D preview
|
|
1745
|
+
createSimplePreview(THREE, texture, canvas);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
/**
|
|
1749
|
+
* Create a simple 3D preview using Three.js
|
|
1750
|
+
* @param {Object} THREE - The Three.js library
|
|
1751
|
+
* @param {THREE.Texture} texture - The texture to display
|
|
1752
|
+
* @param {HTMLCanvasElement} canvas - The canvas to render on
|
|
1753
|
+
*/
|
|
1754
|
+
export function createSimplePreview(THREE, texture, canvas) {
|
|
1755
|
+
// Create renderer
|
|
1756
|
+
const renderer = new THREE.WebGLRenderer({
|
|
1757
|
+
canvas: canvas,
|
|
1758
|
+
alpha: true,
|
|
1759
|
+
antialias: true
|
|
1760
|
+
});
|
|
1761
|
+
renderer.setSize(canvas.width, canvas.height);
|
|
1762
|
+
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
1763
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
1764
|
+
renderer.toneMappingExposure = 1.0;
|
|
1765
|
+
|
|
1766
|
+
// Create scene
|
|
1767
|
+
const scene = new THREE.Scene();
|
|
1768
|
+
scene.background = new THREE.Color(0x111111);
|
|
1769
|
+
|
|
1770
|
+
// Create camera
|
|
1771
|
+
const camera = new THREE.PerspectiveCamera(40, 1, 0.1, 100);
|
|
1772
|
+
camera.position.z = 3;
|
|
1773
|
+
|
|
1774
|
+
// Create sphere with material
|
|
1775
|
+
const geometry = new THREE.SphereGeometry(0.8, 32, 32);
|
|
1776
|
+
const material = new THREE.MeshStandardMaterial({
|
|
1777
|
+
color: 0xffffff,
|
|
1778
|
+
metalness: 1.0,
|
|
1779
|
+
roughness: 0.05,
|
|
1780
|
+
envMapIntensity: 1.0
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1783
|
+
// Set the texture as environment map
|
|
1784
|
+
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
1785
|
+
scene.environment = texture;
|
|
1786
|
+
|
|
1787
|
+
// Create mesh
|
|
1788
|
+
const sphere = new THREE.Mesh(geometry, material);
|
|
1789
|
+
scene.add(sphere);
|
|
1790
|
+
|
|
1791
|
+
// Add lighting
|
|
1792
|
+
const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
|
|
1793
|
+
scene.add(ambientLight);
|
|
1794
|
+
|
|
1795
|
+
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
|
|
1796
|
+
directionalLight.position.set(1, 1, 1);
|
|
1797
|
+
scene.add(directionalLight);
|
|
1798
|
+
|
|
1799
|
+
// Auto rotate the sphere
|
|
1800
|
+
let animationId;
|
|
1801
|
+
function animate() {
|
|
1802
|
+
animationId = requestAnimationFrame(animate);
|
|
1803
|
+
sphere.rotation.y += 0.01;
|
|
1804
|
+
renderer.render(scene, camera);
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
// Start animation
|
|
1808
|
+
animate();
|
|
1809
|
+
|
|
1810
|
+
// Add cleanup on canvas removal
|
|
1811
|
+
const observer = new MutationObserver((mutations) => {
|
|
1812
|
+
for (const mutation of mutations) {
|
|
1813
|
+
if (mutation.type === 'childList' && !document.contains(canvas)) {
|
|
1814
|
+
cancelAnimationFrame(animationId);
|
|
1815
|
+
renderer.dispose();
|
|
1816
|
+
geometry.dispose();
|
|
1817
|
+
material.dispose();
|
|
1818
|
+
observer.disconnect();
|
|
1819
|
+
break;
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1823
|
+
|
|
1824
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// Add event listener for lighting-updated custom event
|
|
1828
|
+
document.addEventListener('lighting-updated', function(e) {
|
|
1829
|
+
console.log('Received lighting-updated event:', e.detail);
|
|
1830
|
+
|
|
1831
|
+
// Show the lighting info section and hide the no data message
|
|
1832
|
+
console.log('Updating UI for example lighting');
|
|
1833
|
+
toggleLightingMessages(true);
|
|
1834
|
+
|
|
1835
|
+
// Update the lighting info with example data
|
|
1836
|
+
const metadata = {
|
|
1837
|
+
fileName: e.detail.description || 'Example Lighting',
|
|
1838
|
+
type: e.detail.type || 'default',
|
|
1839
|
+
description: e.detail.description || 'Default Example Lighting',
|
|
1840
|
+
dimensions: { width: 0, height: 0 },
|
|
1841
|
+
fileSizeBytes: 0
|
|
1842
|
+
};
|
|
1843
|
+
|
|
1844
|
+
// Update UI with simplified metadata
|
|
1845
|
+
const filenameEl = document.getElementById('lighting-filename');
|
|
1846
|
+
const typeEl = document.getElementById('lighting-type');
|
|
1847
|
+
|
|
1848
|
+
if (filenameEl) filenameEl.textContent = metadata.fileName;
|
|
1849
|
+
if (typeEl) typeEl.textContent = metadata.type;
|
|
1850
|
+
|
|
1851
|
+
// Update lighting controls visibility
|
|
1852
|
+
updateSliderVisibility(true);
|
|
1853
|
+
|
|
1854
|
+
// Update lighting message to show information
|
|
1855
|
+
updateLightingMessage();
|
|
1856
|
+
|
|
1857
|
+
console.log('Updated lighting info UI with example lighting data');
|
|
1858
|
+
});
|
|
1859
|
+
|
|
1860
|
+
/**
|
|
1861
|
+
* Set the current background option without triggering UI updates
|
|
1862
|
+
* @param {string} option - The option to set ('none', 'background', or 'hdr')
|
|
1863
|
+
*/
|
|
1864
|
+
export function setCurrentBackgroundOption(option) {
|
|
1865
|
+
if (['none', 'background', 'hdr'].includes(option)) {
|
|
1866
|
+
console.log('Setting current background option to:', option);
|
|
1867
|
+
currentBackgroundOption = option;
|
|
1868
|
+
} else {
|
|
1869
|
+
console.warn('Invalid background option:', option);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
/**
|
|
1874
|
+
* NEW: Add cleanup function for the module
|
|
1875
|
+
*/
|
|
1876
|
+
export function cleanupWorldPanel() {
|
|
1877
|
+
console.log('Cleaning up World Panel...');
|
|
1878
|
+
cleanupEventListeners();
|
|
1879
|
+
|
|
1880
|
+
// Reset state variables
|
|
1881
|
+
controlsInitialized = false;
|
|
1882
|
+
currentLightingMetadata = null;
|
|
1883
|
+
currentBackgroundMetadata = null;
|
|
1884
|
+
environmentTexture = null;
|
|
1885
|
+
backgroundTexture = null;
|
|
1886
|
+
currentBackgroundOption = null;
|
|
1887
|
+
exrLoadInProgress = false;
|
|
1888
|
+
isLoadingEnvironmentFile = false;
|
|
1889
|
+
|
|
1890
|
+
console.log('World Panel cleanup complete');
|
|
1891
|
+
}
|