@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,586 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Texture Debugger - UV Panel Module
|
|
3
|
+
*
|
|
4
|
+
* This module handles UV mapping visualization and controls.
|
|
5
|
+
*/
|
|
6
|
+
import { getState, updateState } from '../../../util/state/scene-state.js';
|
|
7
|
+
import { updateUvRegion } from '../atlas-heading/atlas-heading.js';
|
|
8
|
+
|
|
9
|
+
// DOM elements
|
|
10
|
+
let uvInfoContainer = null;
|
|
11
|
+
let uvManualControls = null;
|
|
12
|
+
let uvOffsetX = null;
|
|
13
|
+
let uvOffsetY = null;
|
|
14
|
+
let uvScaleW = null;
|
|
15
|
+
let uvScaleH = null;
|
|
16
|
+
let uvPredefinedSegments = null;
|
|
17
|
+
let controlsInitialized = false;
|
|
18
|
+
let uvChannelSelectContainer = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Initialize the UV panel and cache DOM elements
|
|
22
|
+
*/
|
|
23
|
+
export function initUvPanel() {
|
|
24
|
+
// Cache DOM elements
|
|
25
|
+
uvInfoContainer = document.getElementById('uv-info-container');
|
|
26
|
+
uvManualControls = document.getElementById('uv-manual-controls');
|
|
27
|
+
uvOffsetX = document.getElementById('uv-offset-x');
|
|
28
|
+
uvOffsetY = document.getElementById('uv-offset-y');
|
|
29
|
+
uvScaleW = document.getElementById('uv-scale-w');
|
|
30
|
+
uvScaleH = document.getElementById('uv-scale-h');
|
|
31
|
+
uvPredefinedSegments = document.getElementById('uv-predefined-segments');
|
|
32
|
+
|
|
33
|
+
// Initial setup
|
|
34
|
+
updateUvPanel();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Update the UV panel with current state
|
|
39
|
+
*/
|
|
40
|
+
export function updateUvPanel() {
|
|
41
|
+
const state = getState();
|
|
42
|
+
|
|
43
|
+
if (!uvInfoContainer || !uvManualControls) {
|
|
44
|
+
// Cache DOM elements if not already done
|
|
45
|
+
uvInfoContainer = document.getElementById('uv-info-container');
|
|
46
|
+
uvManualControls = document.getElementById('uv-manual-controls');
|
|
47
|
+
uvOffsetX = document.getElementById('uv-offset-x');
|
|
48
|
+
uvOffsetY = document.getElementById('uv-offset-y');
|
|
49
|
+
uvScaleW = document.getElementById('uv-scale-w');
|
|
50
|
+
uvScaleH = document.getElementById('uv-scale-h');
|
|
51
|
+
uvPredefinedSegments = document.getElementById('uv-predefined-segments');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!state.model || state.meshes.length === 0) {
|
|
55
|
+
// Hide manual controls and show no data message
|
|
56
|
+
if (uvManualControls) uvManualControls.style.display = 'none';
|
|
57
|
+
if (uvInfoContainer) {
|
|
58
|
+
uvInfoContainer.innerHTML = `
|
|
59
|
+
<p>No model loaded or no UV data available.</p>
|
|
60
|
+
<p>Load a model to view UV information.</p>
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Analyze UV sets in the model
|
|
67
|
+
analyzeUvSets();
|
|
68
|
+
|
|
69
|
+
// Identify display/screen meshes
|
|
70
|
+
identifyScreenMeshes();
|
|
71
|
+
|
|
72
|
+
// If we have UV data, show controls and update info
|
|
73
|
+
if (state.availableUvSets.length > 0) {
|
|
74
|
+
if (uvManualControls) uvManualControls.style.display = 'block';
|
|
75
|
+
updateUvInfo();
|
|
76
|
+
|
|
77
|
+
// Setup UV manual control handlers if not already done
|
|
78
|
+
if (!controlsInitialized) {
|
|
79
|
+
setupUvControls();
|
|
80
|
+
controlsInitialized = true;
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
if (uvManualControls) uvManualControls.style.display = 'none';
|
|
84
|
+
if (uvInfoContainer) {
|
|
85
|
+
uvInfoContainer.innerHTML = `
|
|
86
|
+
<p>No UV data found in this model.</p>
|
|
87
|
+
<p>The model doesn't contain any UV mapping information.</p>
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Update or create UV channel selector for display meshes if needed
|
|
93
|
+
updateUvChannelSelector();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Analyze UV sets available in the model
|
|
98
|
+
*/
|
|
99
|
+
function analyzeUvSets() {
|
|
100
|
+
const state = getState();
|
|
101
|
+
const availableUvSets = [];
|
|
102
|
+
const uvSetNames = [];
|
|
103
|
+
|
|
104
|
+
// Return if no meshes
|
|
105
|
+
if (!state.meshes || state.meshes.length === 0) {
|
|
106
|
+
updateState('availableUvSets', availableUvSets);
|
|
107
|
+
updateState('uvSetNames', uvSetNames);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Collect all available UV sets from all meshes
|
|
112
|
+
const uvSets = new Set();
|
|
113
|
+
state.meshes.forEach(mesh => {
|
|
114
|
+
if (mesh.geometry && mesh.geometry.attributes) {
|
|
115
|
+
Object.keys(mesh.geometry.attributes).forEach(key => {
|
|
116
|
+
if (key === 'uv' || key.startsWith('uv')) {
|
|
117
|
+
uvSets.add(key);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Convert to array and sort
|
|
124
|
+
const availableSets = Array.from(uvSets);
|
|
125
|
+
availableSets.sort(); // Sort for consistent order
|
|
126
|
+
|
|
127
|
+
// Create friendly names
|
|
128
|
+
const setNames = availableSets.map(name => {
|
|
129
|
+
if (name === 'uv') return 'UV Channel 0 (Default)';
|
|
130
|
+
if (name === 'uv2') return 'UV Channel 1 (Secondary)';
|
|
131
|
+
// Extract number for other UV channels
|
|
132
|
+
const match = name.match(/uv(\d+)/);
|
|
133
|
+
if (match) {
|
|
134
|
+
return `UV Channel ${match[1]} (Custom)`;
|
|
135
|
+
}
|
|
136
|
+
return name;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Update state
|
|
140
|
+
updateState('availableUvSets', availableSets);
|
|
141
|
+
updateState('uvSetNames', setNames);
|
|
142
|
+
|
|
143
|
+
// If we don't have a current set but have available sets, select the first
|
|
144
|
+
if ((state.currentUvSet === undefined || state.currentUvSet >= availableSets.length) &&
|
|
145
|
+
availableSets.length > 0) {
|
|
146
|
+
updateState('currentUvSet', 0);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Analyze and collect detailed UV mapping info for each channel
|
|
150
|
+
const uvMappingInfo = {};
|
|
151
|
+
availableSets.forEach(uvChannel => {
|
|
152
|
+
const info = {
|
|
153
|
+
mappingType: 'Standard',
|
|
154
|
+
textureUsage: 'Full Texture',
|
|
155
|
+
meshes: [],
|
|
156
|
+
minU: 1, maxU: 0,
|
|
157
|
+
minV: 1, maxV: 0,
|
|
158
|
+
sampleUVs: null,
|
|
159
|
+
sampleMesh: null
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Find all meshes using this UV channel
|
|
163
|
+
state.meshes.forEach(mesh => {
|
|
164
|
+
if (mesh.geometry && mesh.geometry.attributes && mesh.geometry.attributes[uvChannel]) {
|
|
165
|
+
info.meshes.push(mesh);
|
|
166
|
+
|
|
167
|
+
// Use this as a sample mesh if we don't have one yet
|
|
168
|
+
if (!info.sampleMesh) {
|
|
169
|
+
info.sampleMesh = mesh;
|
|
170
|
+
|
|
171
|
+
// Calculate UV min/max range
|
|
172
|
+
const uvAttr = mesh.geometry.attributes[uvChannel];
|
|
173
|
+
for (let i = 0; i < uvAttr.count; i++) {
|
|
174
|
+
const u = uvAttr.getX(i);
|
|
175
|
+
const v = uvAttr.getY(i);
|
|
176
|
+
info.minU = Math.min(info.minU, u);
|
|
177
|
+
info.minV = Math.min(info.minV, v);
|
|
178
|
+
info.maxU = Math.max(info.maxU, u);
|
|
179
|
+
info.maxV = Math.max(info.maxV, v);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Determine mapping type based on UV range
|
|
186
|
+
if (info.minU < 0 || info.maxU > 1 || info.minV < 0 || info.maxV > 1) {
|
|
187
|
+
info.mappingType = 'Extended Range';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// If we have a good sample of UVs, calculate texture usage
|
|
191
|
+
if (info.maxU - info.minU < 0.5 || info.maxV - info.minV < 0.5) {
|
|
192
|
+
info.textureUsage = 'Partial (Atlas Segment)';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
uvMappingInfo[uvChannel] = info;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
updateState('uvMappingInfo', uvMappingInfo);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Identify screen/display meshes in the model
|
|
203
|
+
*/
|
|
204
|
+
function identifyScreenMeshes() {
|
|
205
|
+
const state = getState();
|
|
206
|
+
|
|
207
|
+
// Return if no meshes
|
|
208
|
+
if (!state.meshes || state.meshes.length === 0) {
|
|
209
|
+
updateState('screenMeshes', []);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Find meshes with names that look like displays
|
|
214
|
+
const screenRegex = /display|screen|monitor|lcd|led|panel|tv/i;
|
|
215
|
+
const screenMeshes = state.meshes.filter(mesh =>
|
|
216
|
+
mesh.name && screenRegex.test(mesh.name)
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Update state
|
|
220
|
+
updateState('screenMeshes', screenMeshes);
|
|
221
|
+
|
|
222
|
+
console.log(`Found ${screenMeshes.length} screen/display meshes:`,
|
|
223
|
+
screenMeshes.map(m => m.name));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Update the UV channel selector for display meshes
|
|
228
|
+
*/
|
|
229
|
+
function updateUvChannelSelector() {
|
|
230
|
+
const state = getState();
|
|
231
|
+
|
|
232
|
+
// Don't show selector if no display meshes or no UV channels
|
|
233
|
+
if (!state.screenMeshes || state.screenMeshes.length === 0 ||
|
|
234
|
+
!state.availableUvSets || state.availableUvSets.length === 0) {
|
|
235
|
+
if (uvChannelSelectContainer) {
|
|
236
|
+
uvChannelSelectContainer.style.display = 'none';
|
|
237
|
+
}
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Create container if it doesn't exist
|
|
242
|
+
if (!uvChannelSelectContainer) {
|
|
243
|
+
uvChannelSelectContainer = document.createElement('div');
|
|
244
|
+
uvChannelSelectContainer.id = 'uv-channel-select-container';
|
|
245
|
+
uvChannelSelectContainer.className = 'uv-control-group';
|
|
246
|
+
uvChannelSelectContainer.style.marginBottom = '15px';
|
|
247
|
+
uvChannelSelectContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.3)';
|
|
248
|
+
uvChannelSelectContainer.style.padding = '10px';
|
|
249
|
+
uvChannelSelectContainer.style.borderRadius = '5px';
|
|
250
|
+
|
|
251
|
+
const title = document.createElement('label');
|
|
252
|
+
title.textContent = 'Display Mesh UV Channel:';
|
|
253
|
+
title.style.display = 'block';
|
|
254
|
+
title.style.marginBottom = '5px';
|
|
255
|
+
title.style.fontWeight = 'bold';
|
|
256
|
+
title.style.color = '#f1c40f';
|
|
257
|
+
uvChannelSelectContainer.appendChild(title);
|
|
258
|
+
|
|
259
|
+
const description = document.createElement('div');
|
|
260
|
+
description.textContent = 'Select UV channel for display meshes';
|
|
261
|
+
description.style.fontSize = '11px';
|
|
262
|
+
description.style.color = '#bbb';
|
|
263
|
+
description.style.marginBottom = '10px';
|
|
264
|
+
uvChannelSelectContainer.appendChild(description);
|
|
265
|
+
|
|
266
|
+
// Create the dropdown
|
|
267
|
+
const select = document.createElement('select');
|
|
268
|
+
select.id = 'display-uv-channel-select';
|
|
269
|
+
select.style.width = '100%';
|
|
270
|
+
select.style.padding = '5px';
|
|
271
|
+
select.style.backgroundColor = '#333';
|
|
272
|
+
select.style.border = '1px solid #555';
|
|
273
|
+
select.style.borderRadius = '3px';
|
|
274
|
+
select.style.color = 'white';
|
|
275
|
+
|
|
276
|
+
// Add event listener
|
|
277
|
+
select.addEventListener('change', function() {
|
|
278
|
+
switchUvChannelForDisplays(this.value);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
uvChannelSelectContainer.appendChild(select);
|
|
282
|
+
|
|
283
|
+
// Show count of display meshes
|
|
284
|
+
const screenCount = document.createElement('div');
|
|
285
|
+
screenCount.id = 'screen-mesh-count';
|
|
286
|
+
screenCount.style.marginTop = '5px';
|
|
287
|
+
screenCount.style.fontSize = '11px';
|
|
288
|
+
screenCount.style.color = '#3498db';
|
|
289
|
+
uvChannelSelectContainer.appendChild(screenCount);
|
|
290
|
+
|
|
291
|
+
// Add to UV panel container
|
|
292
|
+
if (uvManualControls && uvManualControls.parentNode) {
|
|
293
|
+
uvManualControls.parentNode.insertBefore(uvChannelSelectContainer, uvManualControls);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Update dropdown options
|
|
298
|
+
const select = document.getElementById('display-uv-channel-select');
|
|
299
|
+
if (select) {
|
|
300
|
+
// Clear existing options
|
|
301
|
+
select.innerHTML = '';
|
|
302
|
+
|
|
303
|
+
// Add options for each UV set
|
|
304
|
+
state.availableUvSets.forEach((uvSet, index) => {
|
|
305
|
+
const option = document.createElement('option');
|
|
306
|
+
option.value = uvSet;
|
|
307
|
+
option.textContent = state.uvSetNames[index];
|
|
308
|
+
select.appendChild(option);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Set default selection to match current UV set
|
|
312
|
+
if (state.currentUvSet !== undefined && state.availableUvSets[state.currentUvSet]) {
|
|
313
|
+
select.value = state.availableUvSets[state.currentUvSet];
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Update screen mesh count
|
|
318
|
+
const screenCount = document.getElementById('screen-mesh-count');
|
|
319
|
+
if (screenCount) {
|
|
320
|
+
screenCount.textContent = `Found ${state.screenMeshes.length} display/screen meshes`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Show the container
|
|
324
|
+
uvChannelSelectContainer.style.display = 'block';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Switch UV channel for all display meshes
|
|
329
|
+
* @param {string} uvChannel - The UV channel to switch to
|
|
330
|
+
*/
|
|
331
|
+
function switchUvChannelForDisplays(uvChannel) {
|
|
332
|
+
const state = getState();
|
|
333
|
+
|
|
334
|
+
if (!state.screenMeshes || state.screenMeshes.length === 0) {
|
|
335
|
+
console.warn('No display meshes to update');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
console.log(`Switching ${state.screenMeshes.length} display meshes to UV channel: ${uvChannel}`);
|
|
340
|
+
|
|
341
|
+
state.screenMeshes.forEach(mesh => {
|
|
342
|
+
// Skip if mesh doesn't have this UV channel
|
|
343
|
+
if (!mesh.geometry || !mesh.geometry.attributes || !mesh.geometry.attributes[uvChannel]) {
|
|
344
|
+
console.warn(`Mesh ${mesh.name} doesn't have UV channel ${uvChannel}`);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Get the UV attribute
|
|
349
|
+
const uvAttribute = mesh.geometry.attributes[uvChannel];
|
|
350
|
+
|
|
351
|
+
// Skip if mesh doesn't have a material
|
|
352
|
+
if (!mesh.material) {
|
|
353
|
+
console.warn(`Mesh ${mesh.name} doesn't have a material`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// If mesh has material array, update all materials
|
|
358
|
+
if (Array.isArray(mesh.material)) {
|
|
359
|
+
mesh.material.forEach(mat => {
|
|
360
|
+
if (mat.map) {
|
|
361
|
+
mat.map.needsUpdate = true;
|
|
362
|
+
}
|
|
363
|
+
mat.needsUpdate = true;
|
|
364
|
+
});
|
|
365
|
+
} else {
|
|
366
|
+
// Update textures
|
|
367
|
+
if (mesh.material.map) {
|
|
368
|
+
mesh.material.map.needsUpdate = true;
|
|
369
|
+
}
|
|
370
|
+
mesh.material.needsUpdate = true;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Update mesh with new UV channel
|
|
374
|
+
mesh.geometry.setAttribute('uv', uvAttribute);
|
|
375
|
+
mesh.geometry.attributes.uv.needsUpdate = true;
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Register the function for switching UV channels
|
|
379
|
+
if (!state.switchUvChannel) {
|
|
380
|
+
updateState('switchUvChannel', switchUvChannelForDisplays);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Update UV info
|
|
384
|
+
updateUvInfo();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Update UV info display
|
|
389
|
+
*/
|
|
390
|
+
function updateUvInfo() {
|
|
391
|
+
const state = getState();
|
|
392
|
+
|
|
393
|
+
// Exit if no UV sets or no container
|
|
394
|
+
if (state.availableUvSets.length === 0 || !uvInfoContainer) return;
|
|
395
|
+
|
|
396
|
+
// Get current UV set
|
|
397
|
+
const currentSetName = state.availableUvSets[state.currentUvSet] || 'uv';
|
|
398
|
+
|
|
399
|
+
// Build HTML content
|
|
400
|
+
let content = '<div style="color: #f1c40f; font-weight: bold;">UV Channel Info:</div>';
|
|
401
|
+
content += `<div>Channel Name: <span style="color: #3498db">${currentSetName}</span></div>`;
|
|
402
|
+
|
|
403
|
+
// Add display mesh info if available
|
|
404
|
+
const displayMeshesWithUv = state.screenMeshes.filter(mesh =>
|
|
405
|
+
mesh.geometry && mesh.geometry.attributes &&
|
|
406
|
+
mesh.geometry.attributes[currentSetName]);
|
|
407
|
+
|
|
408
|
+
if (displayMeshesWithUv.length > 0) {
|
|
409
|
+
content += `<div style="margin-top: 5px; color: #e74c3c;">Display Meshes: <span style="color: white">${displayMeshesWithUv.length}</span></div>`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Get a sample mesh that has this UV set
|
|
413
|
+
const sampleMesh = state.meshes.find(mesh =>
|
|
414
|
+
mesh.geometry && mesh.geometry.attributes &&
|
|
415
|
+
mesh.geometry.attributes[currentSetName]);
|
|
416
|
+
|
|
417
|
+
if (sampleMesh) {
|
|
418
|
+
// Get UV attribute
|
|
419
|
+
const uvAttr = sampleMesh.geometry.attributes[currentSetName];
|
|
420
|
+
|
|
421
|
+
// Add sample UV coordinates
|
|
422
|
+
content += '<div style="margin-top: 5px; color: #f1c40f;">Sample UV Coordinates:</div>';
|
|
423
|
+
content += `<div>From: <span style="color: #3498db">${sampleMesh.name || 'Unnamed mesh'}</span></div>`;
|
|
424
|
+
|
|
425
|
+
// Get a few sample vertices
|
|
426
|
+
const sampleCount = Math.min(5, uvAttr.count);
|
|
427
|
+
let minU = 1, minV = 1, maxU = 0, maxV = 0;
|
|
428
|
+
|
|
429
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
430
|
+
const u = uvAttr.getX(i);
|
|
431
|
+
const v = uvAttr.getY(i);
|
|
432
|
+
minU = Math.min(minU, u);
|
|
433
|
+
minV = Math.min(minV, v);
|
|
434
|
+
maxU = Math.max(maxU, u);
|
|
435
|
+
maxV = Math.max(maxV, v);
|
|
436
|
+
content += `<div>Vertex ${i}: <span style="color: #3498db">(${u.toFixed(4)}, ${v.toFixed(4)})</span></div>`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (uvAttr.count > sampleCount) {
|
|
440
|
+
content += `<div>... and ${uvAttr.count - sampleCount} more vertices</div>`;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Add UV range
|
|
444
|
+
content += '<div style="margin-top: 5px; color: #f1c40f;">UV Range:</div>';
|
|
445
|
+
content += `<div>U: <span style="color: #3498db">${minU.toFixed(4)} to ${maxU.toFixed(4)}</span></div>`;
|
|
446
|
+
content += `<div>V: <span style="color: #3498db">${minV.toFixed(4)} to ${maxV.toFixed(4)}</span></div>`;
|
|
447
|
+
|
|
448
|
+
// Add mesh statistics
|
|
449
|
+
const meshesWithUv = state.meshes.filter(mesh =>
|
|
450
|
+
mesh.geometry && mesh.geometry.attributes &&
|
|
451
|
+
mesh.geometry.attributes[currentSetName]);
|
|
452
|
+
|
|
453
|
+
content += '<div style="margin-top: 5px; color: #f1c40f;">Mesh Statistics:</div>';
|
|
454
|
+
content += `<div>Meshes with this UV: <span style="color: #3498db">${meshesWithUv.length} of ${state.meshes.length}</span></div>`;
|
|
455
|
+
} else {
|
|
456
|
+
content += '<div style="color: #e74c3c;">No meshes use this UV channel</div>';
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
uvInfoContainer.innerHTML = content;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Setup UV manual controls
|
|
464
|
+
*/
|
|
465
|
+
function setupUvControls() {
|
|
466
|
+
if (!uvOffsetX || !uvOffsetY || !uvScaleW || !uvScaleH || !uvPredefinedSegments) return;
|
|
467
|
+
|
|
468
|
+
// Define common segment options
|
|
469
|
+
const segments = [
|
|
470
|
+
{ name: 'Full texture (1×1)', u: 0, v: 0, w: 1, h: 1 },
|
|
471
|
+
{ name: 'Top-left quarter (1/2×1/2)', u: 0, v: 0, w: 0.5, h: 0.5 },
|
|
472
|
+
{ name: 'Top-right quarter (1/2×1/2)', u: 0.5, v: 0, w: 0.5, h: 0.5 },
|
|
473
|
+
{ name: 'Bottom-left quarter (1/2×1/2)', u: 0, v: 0.5, w: 0.5, h: 0.5 },
|
|
474
|
+
{ name: 'Bottom-right quarter (1/2×1/2)', u: 0.5, v: 0.5, w: 0.5, h: 0.5 },
|
|
475
|
+
{ name: 'Top-left ninth (1/3×1/3)', u: 0, v: 0, w: 0.33, h: 0.33 },
|
|
476
|
+
{ name: 'Top-center ninth (1/3×1/3)', u: 0.33, v: 0, w: 0.33, h: 0.33 },
|
|
477
|
+
{ name: 'Top-right ninth (1/3×1/3)', u: 0.66, v: 0, w: 0.33, h: 0.33 },
|
|
478
|
+
{ name: 'Middle-left ninth (1/3×1/3)', u: 0, v: 0.33, w: 0.33, h: 0.33 }
|
|
479
|
+
];
|
|
480
|
+
|
|
481
|
+
// Clear and populate predefined segments dropdown
|
|
482
|
+
uvPredefinedSegments.innerHTML = '';
|
|
483
|
+
segments.forEach((segment, index) => {
|
|
484
|
+
const option = document.createElement('option');
|
|
485
|
+
option.value = index;
|
|
486
|
+
option.textContent = segment.name;
|
|
487
|
+
uvPredefinedSegments.appendChild(option);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
// Function to apply mapping changes
|
|
491
|
+
const applyMapping = () => {
|
|
492
|
+
// Get values
|
|
493
|
+
const offsetX = parseFloat(uvOffsetX.value) || 0;
|
|
494
|
+
const offsetY = parseFloat(uvOffsetY.value) || 0;
|
|
495
|
+
const scaleW = parseFloat(uvScaleW.value) || 1;
|
|
496
|
+
const scaleH = parseFloat(uvScaleH.value) || 1;
|
|
497
|
+
|
|
498
|
+
// Check if all values are valid
|
|
499
|
+
if (offsetX < 0 || offsetX > 1 ||
|
|
500
|
+
offsetY < 0 || offsetY > 1 ||
|
|
501
|
+
scaleW <= 0 || scaleW > 1 ||
|
|
502
|
+
scaleH <= 0 || scaleH > 1) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const state = getState();
|
|
507
|
+
|
|
508
|
+
// Apply mapping to all meshes (or just display meshes if available)
|
|
509
|
+
const targetMeshes = state.screenMeshes.length > 0 ? state.screenMeshes : state.meshes;
|
|
510
|
+
|
|
511
|
+
targetMeshes.forEach(mesh => {
|
|
512
|
+
if (mesh.material) {
|
|
513
|
+
// Apply to all texture maps
|
|
514
|
+
const textureMaps = ['map', 'normalMap', 'aoMap', 'roughnessMap', 'metalnessMap', 'alphaMap'];
|
|
515
|
+
|
|
516
|
+
textureMaps.forEach(mapName => {
|
|
517
|
+
if (mesh.material[mapName]) {
|
|
518
|
+
mesh.material[mapName].offset.set(offsetX, offsetY);
|
|
519
|
+
mesh.material[mapName].repeat.set(scaleW, scaleH);
|
|
520
|
+
mesh.material[mapName].needsUpdate = true;
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
mesh.material.needsUpdate = true;
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// Update the current UV region for visualization
|
|
529
|
+
updateUvRegion([offsetX, offsetY], [offsetX + scaleW, offsetY + scaleH]);
|
|
530
|
+
|
|
531
|
+
// Update our state function
|
|
532
|
+
if (!state.setCurrentUvRegion) {
|
|
533
|
+
updateState('setCurrentUvRegion', (min, max) => {
|
|
534
|
+
updateUvRegion(min, max);
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// Set up event listeners
|
|
540
|
+
uvOffsetX.addEventListener('input', applyMapping);
|
|
541
|
+
uvOffsetY.addEventListener('input', applyMapping);
|
|
542
|
+
uvScaleW.addEventListener('input', applyMapping);
|
|
543
|
+
uvScaleH.addEventListener('input', applyMapping);
|
|
544
|
+
|
|
545
|
+
// Set up predefined segments
|
|
546
|
+
uvPredefinedSegments.addEventListener('change', function() {
|
|
547
|
+
const selectedSegment = segments[this.value];
|
|
548
|
+
|
|
549
|
+
// Update input fields
|
|
550
|
+
uvOffsetX.value = selectedSegment.u;
|
|
551
|
+
uvOffsetY.value = selectedSegment.v;
|
|
552
|
+
uvScaleW.value = selectedSegment.w;
|
|
553
|
+
uvScaleH.value = selectedSegment.h;
|
|
554
|
+
|
|
555
|
+
// Apply the mapping immediately
|
|
556
|
+
applyMapping();
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// Initialize values
|
|
560
|
+
uvOffsetX.value = 0;
|
|
561
|
+
uvOffsetY.value = 0;
|
|
562
|
+
uvScaleW.value = 1;
|
|
563
|
+
uvScaleH.value = 1;
|
|
564
|
+
|
|
565
|
+
// Create atlas segment cycle function
|
|
566
|
+
const state = getState();
|
|
567
|
+
const cycleAtlasSegments = () => {
|
|
568
|
+
// Get the current segment index
|
|
569
|
+
let currentIndex = parseInt(uvPredefinedSegments.value);
|
|
570
|
+
// Move to the next segment
|
|
571
|
+
currentIndex = (currentIndex + 1) % segments.length;
|
|
572
|
+
// Update the dropdown
|
|
573
|
+
uvPredefinedSegments.value = currentIndex;
|
|
574
|
+
// Trigger the change
|
|
575
|
+
const event = new Event('change');
|
|
576
|
+
uvPredefinedSegments.dispatchEvent(event);
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
// Register the function in the state
|
|
580
|
+
updateState('cycleAtlasSegments', cycleAtlasSegments);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export default {
|
|
584
|
+
initUvPanel,
|
|
585
|
+
updateUvPanel
|
|
586
|
+
};
|