@littlecarlito/blorktools 0.50.3 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/bin/cli.js +69 -0
  2. package/package.json +13 -7
  3. package/src/asset_debugger/axis-indicator/axis-indicator.css +6 -0
  4. package/src/asset_debugger/axis-indicator/axis-indicator.html +20 -0
  5. package/src/asset_debugger/axis-indicator/axis-indicator.js +822 -0
  6. package/src/asset_debugger/debugger-scene/debugger-scene.css +142 -0
  7. package/src/asset_debugger/debugger-scene/debugger-scene.html +80 -0
  8. package/src/asset_debugger/debugger-scene/debugger-scene.js +791 -0
  9. package/src/asset_debugger/header/header.css +73 -0
  10. package/src/asset_debugger/header/header.html +24 -0
  11. package/src/asset_debugger/header/header.js +224 -0
  12. package/src/asset_debugger/index.html +76 -0
  13. package/src/asset_debugger/landing-page/landing-page.css +396 -0
  14. package/src/asset_debugger/landing-page/landing-page.html +81 -0
  15. package/src/asset_debugger/landing-page/landing-page.js +611 -0
  16. package/src/asset_debugger/loading-splash/loading-splash.css +195 -0
  17. package/src/asset_debugger/loading-splash/loading-splash.html +22 -0
  18. package/src/asset_debugger/loading-splash/loading-splash.js +59 -0
  19. package/src/asset_debugger/loading-splash/preview-loading-splash.js +66 -0
  20. package/src/asset_debugger/main.css +14 -0
  21. package/src/asset_debugger/modals/examples-modal/examples-modal.css +41 -0
  22. package/src/asset_debugger/modals/examples-modal/examples-modal.html +18 -0
  23. package/src/asset_debugger/modals/examples-modal/examples-modal.js +111 -0
  24. package/src/asset_debugger/modals/examples-modal/examples.js +125 -0
  25. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.css +452 -0
  26. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.html +87 -0
  27. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.js +675 -0
  28. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.css +219 -0
  29. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.html +20 -0
  30. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.js +548 -0
  31. package/src/asset_debugger/modals/settings-modal/settings-modal.css +103 -0
  32. package/src/asset_debugger/modals/settings-modal/settings-modal.html +158 -0
  33. package/src/asset_debugger/modals/settings-modal/settings-modal.js +475 -0
  34. package/src/asset_debugger/panels/asset-panel/asset-panel.css +263 -0
  35. package/src/asset_debugger/panels/asset-panel/asset-panel.html +123 -0
  36. package/src/asset_debugger/panels/asset-panel/asset-panel.js +136 -0
  37. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.css +94 -0
  38. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.js +312 -0
  39. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.css +129 -0
  40. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.js +486 -0
  41. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.css +545 -0
  42. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.js +538 -0
  43. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.css +70 -0
  44. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.js +586 -0
  45. package/src/asset_debugger/panels/world-panel/world-panel.css +364 -0
  46. package/src/asset_debugger/panels/world-panel/world-panel.html +173 -0
  47. package/src/asset_debugger/panels/world-panel/world-panel.js +1891 -0
  48. package/src/asset_debugger/router.js +190 -0
  49. package/src/asset_debugger/util/animation/playback/animation-playback-controller.js +150 -0
  50. package/src/asset_debugger/util/animation/playback/animation-preview-controller.js +316 -0
  51. package/src/asset_debugger/util/animation/playback/css3d-bounce-controller.js +400 -0
  52. package/src/asset_debugger/util/animation/playback/css3d-reversal-controller.js +821 -0
  53. package/src/asset_debugger/util/animation/render/css3d-prerender-controller.js +696 -0
  54. package/src/asset_debugger/util/animation/render/debug-texture-factory.js +0 -0
  55. package/src/asset_debugger/util/animation/render/iframe2texture-render-controller.js +199 -0
  56. package/src/asset_debugger/util/animation/render/image2texture-prerender-controller.js +461 -0
  57. package/src/asset_debugger/util/animation/render/pbr-material-factory.js +82 -0
  58. package/src/asset_debugger/util/common.css +280 -0
  59. package/src/asset_debugger/util/data/animation-classifier.js +323 -0
  60. package/src/asset_debugger/util/data/duplicate-handler.js +20 -0
  61. package/src/asset_debugger/util/data/glb-buffer-manager.js +407 -0
  62. package/src/asset_debugger/util/data/glb-classifier.js +290 -0
  63. package/src/asset_debugger/util/data/html-formatter.js +76 -0
  64. package/src/asset_debugger/util/data/html-linter.js +276 -0
  65. package/src/asset_debugger/util/data/localstorage-manager.js +265 -0
  66. package/src/asset_debugger/util/data/mesh-html-manager.js +295 -0
  67. package/src/asset_debugger/util/data/string-serder.js +303 -0
  68. package/src/asset_debugger/util/data/texture-classifier.js +663 -0
  69. package/src/asset_debugger/util/data/upload/background-file-handler.js +292 -0
  70. package/src/asset_debugger/util/data/upload/dropzone-preview-controller.js +396 -0
  71. package/src/asset_debugger/util/data/upload/file-upload-manager.js +495 -0
  72. package/src/asset_debugger/util/data/upload/glb-file-handler.js +36 -0
  73. package/src/asset_debugger/util/data/upload/glb-preview-controller.js +317 -0
  74. package/src/asset_debugger/util/data/upload/lighting-file-handler.js +194 -0
  75. package/src/asset_debugger/util/data/upload/model-file-manager.js +104 -0
  76. package/src/asset_debugger/util/data/upload/texture-file-handler.js +166 -0
  77. package/src/asset_debugger/util/data/upload/zip-handler.js +686 -0
  78. package/src/asset_debugger/util/loaders/html2canvas-loader.js +107 -0
  79. package/src/asset_debugger/util/rig/bone-kinematics.js +403 -0
  80. package/src/asset_debugger/util/rig/rig-constraint-manager.js +618 -0
  81. package/src/asset_debugger/util/rig/rig-controller.js +612 -0
  82. package/src/asset_debugger/util/rig/rig-factory.js +628 -0
  83. package/src/asset_debugger/util/rig/rig-handle-factory.js +46 -0
  84. package/src/asset_debugger/util/rig/rig-label-factory.js +441 -0
  85. package/src/asset_debugger/util/rig/rig-mouse-handler.js +377 -0
  86. package/src/asset_debugger/util/rig/rig-state-manager.js +175 -0
  87. package/src/asset_debugger/util/rig/rig-tooltip-manager.js +267 -0
  88. package/src/asset_debugger/util/rig/rig-ui-factory.js +700 -0
  89. package/src/asset_debugger/util/scene/background-manager.js +284 -0
  90. package/src/asset_debugger/util/scene/camera-controller.js +243 -0
  91. package/src/asset_debugger/util/scene/css3d-debug-controller.js +406 -0
  92. package/src/asset_debugger/util/scene/css3d-frame-factory.js +113 -0
  93. package/src/asset_debugger/util/scene/css3d-scene-manager.js +529 -0
  94. package/src/asset_debugger/util/scene/glb-controller.js +208 -0
  95. package/src/asset_debugger/util/scene/lighting-manager.js +690 -0
  96. package/src/asset_debugger/util/scene/threejs-model-manager.js +437 -0
  97. package/src/asset_debugger/util/scene/threejs-preview-manager.js +207 -0
  98. package/src/asset_debugger/util/scene/threejs-preview-setup.js +478 -0
  99. package/src/asset_debugger/util/scene/threejs-scene-controller.js +286 -0
  100. package/src/asset_debugger/util/scene/ui-manager.js +107 -0
  101. package/src/asset_debugger/util/state/animation-state.js +128 -0
  102. package/src/asset_debugger/util/state/css3d-state.js +83 -0
  103. package/src/asset_debugger/util/state/glb-preview-state.js +31 -0
  104. package/src/asset_debugger/util/state/log-util.js +197 -0
  105. package/src/asset_debugger/util/state/scene-state.js +452 -0
  106. package/src/asset_debugger/util/state/threejs-state.js +54 -0
  107. package/src/asset_debugger/util/workers/lighting-worker.js +61 -0
  108. package/src/asset_debugger/util/workers/model-worker.js +109 -0
  109. package/src/asset_debugger/util/workers/texture-worker.js +54 -0
  110. package/src/asset_debugger/util/workers/worker-manager.js +212 -0
  111. package/src/asset_debugger/widgets/mesh-info-widget.js +280 -0
  112. package/src/index.html +261 -0
  113. package/src/index.js +8 -0
  114. package/vite.config.js +66 -0
@@ -0,0 +1,686 @@
1
+ import { formatFileSize } from "./file-upload-manager";
2
+ import { TextureClassifier } from "../texture-classifier";
3
+ import { updateState } from "../../state/scene-state";
4
+
5
+ const SUPPORTED_IMAGE_FORMATS = ['.png', '.jpg', '.jpeg', '.webp', '.tiff', '.tif'];
6
+ const SUPPORTED_MODEL_FORMATS = ['.glb', '.gltf'];
7
+ const SUPPORTED_ENV_FORMATS = ['.hdr', '.exr'];
8
+ const SUPPORTED_BG_FORMATS = [...SUPPORTED_ENV_FORMATS, ...SUPPORTED_IMAGE_FORMATS];
9
+ const MACOS_METADATA_PATTERN = /^__MACOSX\/|\/\._|\._/;
10
+
11
+ const TEXTURE_NAME_PATTERNS = {
12
+ baseColor: /\b(base[_-]?color|diffuse|albedo|base|col|color|basecolor)\b/i,
13
+ normalMap: /\b(normal|nrm|nor|nrml|norm)\b/i,
14
+ ormMap: /\b(orm|occlusion|roughness|metalness|metallic|rough|metal|ao|ambient[_-]?occlusion)\b/i
15
+ };
16
+
17
+ const BACKGROUND_NAME_PATTERNS = /\b(background|back|bg|backdrop|environment|env|sky|hdri)\b/i;
18
+
19
+ async function loadJSZip() {
20
+ try {
21
+ try {
22
+ const JSZipModule = await import('jszip');
23
+ return JSZipModule.default || JSZipModule;
24
+ } catch (e) {
25
+ const jsZipUrl = 'https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js';
26
+
27
+ return new Promise((resolve, reject) => {
28
+ const script = document.createElement('script');
29
+ script.src = jsZipUrl;
30
+ script.onload = () => {
31
+ if (window.JSZip) {
32
+ resolve(window.JSZip);
33
+ } else {
34
+ reject(new Error('JSZip loaded from CDN but not available on window'));
35
+ }
36
+ };
37
+ script.onerror = () => {
38
+ reject(new Error('Failed to load JSZip from CDN'));
39
+ };
40
+ document.head.appendChild(script);
41
+ });
42
+ }
43
+ } catch (error) {
44
+ throw new Error('Failed to load JSZip: ' + error.message);
45
+ }
46
+ }
47
+
48
+ function isMacOSMetadataFile(filename) {
49
+ return MACOS_METADATA_PATTERN.test(filename);
50
+ }
51
+
52
+ function detectTextureTypeFromFilename(filename) {
53
+ const lowerFilename = filename.toLowerCase();
54
+ const baseName = lowerFilename.split('/').pop();
55
+
56
+ if (TEXTURE_NAME_PATTERNS.baseColor.test(baseName)) {
57
+ return { type: 'basecolor', confidence: 0.9 };
58
+ } else if (TEXTURE_NAME_PATTERNS.normalMap.test(baseName)) {
59
+ return { type: 'normal', confidence: 0.9 };
60
+ } else if (TEXTURE_NAME_PATTERNS.ormMap.test(baseName)) {
61
+ return { type: 'orm', confidence: 0.9 };
62
+ }
63
+
64
+ if (baseName.includes('_color') || baseName.includes('-color')) {
65
+ return { type: 'basecolor', confidence: 0.7 };
66
+ } else if (baseName.includes('_normal') || baseName.includes('-normal')) {
67
+ return { type: 'normal', confidence: 0.7 };
68
+ } else if (baseName.includes('_orm') || baseName.includes('-orm')) {
69
+ return { type: 'orm', confidence: 0.7 };
70
+ }
71
+
72
+ if (baseName.includes('color') || baseName.includes('col') || baseName.includes('albedo')) {
73
+ return { type: 'basecolor', confidence: 0.5 };
74
+ } else if (baseName.includes('nrm') || baseName.includes('nor')) {
75
+ return { type: 'normal', confidence: 0.5 };
76
+ } else if (baseName.includes('rough') || baseName.includes('metal') || baseName.includes('ao')) {
77
+ return { type: 'orm', confidence: 0.5 };
78
+ }
79
+
80
+ return { type: null, confidence: 0 };
81
+ }
82
+
83
+ function isLikelyBackgroundImage(filename) {
84
+ const lowerFilename = filename.toLowerCase();
85
+ const baseName = lowerFilename.split('/').pop();
86
+
87
+ return BACKGROUND_NAME_PATTERNS.test(baseName);
88
+ }
89
+
90
+ export async function processZipContents(zipFile) {
91
+ try {
92
+ const JSZip = await loadJSZip();
93
+
94
+ const zip = new JSZip();
95
+ const zipData = await zip.loadAsync(zipFile);
96
+
97
+ const extractedFiles = {
98
+ images: [],
99
+ models: [],
100
+ environmentMaps: [],
101
+ backgroundImages: [],
102
+ otherFiles: []
103
+ };
104
+
105
+ const processedFiles = [];
106
+
107
+ for (const [filename, fileObj] of Object.entries(zipData.files)) {
108
+ if (fileObj.dir || isMacOSMetadataFile(filename)) {
109
+ continue;
110
+ }
111
+
112
+ const extension = filename.substring(filename.lastIndexOf('.')).toLowerCase();
113
+
114
+ if (SUPPORTED_IMAGE_FORMATS.includes(extension)) {
115
+ const blob = await fileObj.async('blob');
116
+ const file = new File([blob], filename, { type: getImageMimeType(extension) });
117
+
118
+ const detectedType = detectTextureTypeFromFilename(filename);
119
+ const isBackground = isLikelyBackgroundImage(filename);
120
+
121
+ extractedFiles.images.push({
122
+ file,
123
+ name: filename,
124
+ extension,
125
+ detectedType: detectedType.type,
126
+ confidence: detectedType.confidence,
127
+ isBackground
128
+ });
129
+
130
+ if (isBackground) {
131
+ extractedFiles.backgroundImages.push({
132
+ file,
133
+ name: filename,
134
+ extension,
135
+ isNamed: true
136
+ });
137
+ } else {
138
+ extractedFiles.backgroundImages.push({
139
+ file,
140
+ name: filename,
141
+ extension,
142
+ isNamed: false
143
+ });
144
+ }
145
+
146
+ processedFiles.push({
147
+ file,
148
+ name: filename,
149
+ type: 'image',
150
+ extension,
151
+ detectedType: detectedType.type,
152
+ confidence: detectedType.confidence,
153
+ isBackground
154
+ });
155
+ }
156
+ else if (SUPPORTED_MODEL_FORMATS.includes(extension)) {
157
+ const blob = await fileObj.async('blob');
158
+ const file = new File([blob], filename, { type: 'model/gltf-binary' });
159
+
160
+ extractedFiles.models.push({
161
+ file,
162
+ name: filename,
163
+ extension
164
+ });
165
+
166
+ processedFiles.push({
167
+ file,
168
+ name: filename,
169
+ type: 'model',
170
+ extension
171
+ });
172
+ }
173
+ else if (SUPPORTED_ENV_FORMATS.includes(extension)) {
174
+ const blob = await fileObj.async('blob');
175
+ const file = new File([blob], filename, { type: 'application/octet-stream' });
176
+
177
+ extractedFiles.environmentMaps.push({
178
+ file,
179
+ name: filename,
180
+ extension
181
+ });
182
+
183
+ extractedFiles.backgroundImages.push({
184
+ file,
185
+ name: filename,
186
+ extension,
187
+ isNamed: isLikelyBackgroundImage(filename)
188
+ });
189
+
190
+ processedFiles.push({
191
+ file,
192
+ name: filename,
193
+ type: 'environment',
194
+ extension
195
+ });
196
+ }
197
+ else {
198
+ const blob = await fileObj.async('blob');
199
+ const file = new File([blob], filename, { type: 'application/octet-stream' });
200
+
201
+ extractedFiles.otherFiles.push({
202
+ file,
203
+ name: filename,
204
+ extension
205
+ });
206
+
207
+ processedFiles.push({
208
+ file,
209
+ name: filename,
210
+ type: 'other',
211
+ extension
212
+ });
213
+ }
214
+ }
215
+
216
+ const atlasResults = await determineTextureTypes(extractedFiles.images);
217
+
218
+ const modelFile = extractedFiles.models.length > 0 ? extractedFiles.models[0].file : null;
219
+ const lightingFile = extractedFiles.environmentMaps.length > 0 ? extractedFiles.environmentMaps[0].file : null;
220
+
221
+ extractedFiles.backgroundImages.sort((a, b) => {
222
+ if (a.isNamed && !b.isNamed) return -1;
223
+ if (!a.isNamed && b.isNamed) return 1;
224
+ return 0;
225
+ });
226
+
227
+ const backgroundFile = extractedFiles.backgroundImages.length > 0 ?
228
+ extractedFiles.backgroundImages[0].file : null;
229
+
230
+ const results = {
231
+ success: true,
232
+ extractedFiles,
233
+ processedFiles,
234
+ atlasResults,
235
+ modelFile,
236
+ lightingFile,
237
+ backgroundFile,
238
+ hasNamedBackground: extractedFiles.backgroundImages.length > 0 && extractedFiles.backgroundImages[0].isNamed
239
+ };
240
+
241
+ handleAutoLoad(results);
242
+
243
+ return results;
244
+ } catch (error) {
245
+ return {
246
+ success: false,
247
+ error: error.message
248
+ };
249
+ }
250
+ }
251
+
252
+ export function handleAutoLoad(results) {
253
+ if (!results.success) return;
254
+
255
+ if (results.atlasResults) {
256
+ if (results.atlasResults.baseColor) {
257
+ loadTextureIntoDropzone(results.atlasResults.baseColor.file, 'basecolor');
258
+ }
259
+ if (results.atlasResults.normalMap) {
260
+ loadTextureIntoDropzone(results.atlasResults.normalMap.file, 'normal');
261
+ }
262
+ if (results.atlasResults.ormMap) {
263
+ loadTextureIntoDropzone(results.atlasResults.ormMap.file, 'orm');
264
+ }
265
+ }
266
+
267
+ if (results.modelFile) {
268
+ loadModelIntoDropzone(results.modelFile);
269
+ }
270
+
271
+ if (results.lightingFile) {
272
+ loadLightingIntoDropzone(results.lightingFile);
273
+ }
274
+
275
+ if (results.backgroundFile && results.hasNamedBackground) {
276
+ loadBackgroundIntoDropzone(results.backgroundFile);
277
+ }
278
+ }
279
+
280
+ async function determineTextureTypes(images) {
281
+ const result = {
282
+ baseColor: null,
283
+ normalMap: null,
284
+ ormMap: null,
285
+ unclassified: []
286
+ };
287
+
288
+ if (!images || images.length === 0) {
289
+ return result;
290
+ }
291
+
292
+ const sortedImages = [...images].sort((a, b) => {
293
+ return (b.confidence || 0) - (a.confidence || 0);
294
+ });
295
+
296
+ for (const image of sortedImages) {
297
+ if (image.confidence >= 0.7) {
298
+ if (image.detectedType === 'basecolor' && !result.baseColor) {
299
+ result.baseColor = {
300
+ file: image.file,
301
+ fileName: image.name,
302
+ source: 'name_detection_high_confidence'
303
+ };
304
+ } else if (image.detectedType === 'normal' && !result.normalMap) {
305
+ result.normalMap = {
306
+ file: image.file,
307
+ fileName: image.name,
308
+ source: 'name_detection_high_confidence'
309
+ };
310
+ } else if (image.detectedType === 'orm' && !result.ormMap) {
311
+ result.ormMap = {
312
+ file: image.file,
313
+ fileName: image.name,
314
+ source: 'name_detection_high_confidence'
315
+ };
316
+ }
317
+ }
318
+ }
319
+
320
+ for (const image of sortedImages) {
321
+ if (image.confidence >= 0.5 && image.confidence < 0.7) {
322
+ if (image.detectedType === 'basecolor' && !result.baseColor) {
323
+ result.baseColor = {
324
+ file: image.file,
325
+ fileName: image.name,
326
+ source: 'name_detection_medium_confidence'
327
+ };
328
+ } else if (image.detectedType === 'normal' && !result.normalMap) {
329
+ result.normalMap = {
330
+ file: image.file,
331
+ fileName: image.name,
332
+ source: 'name_detection_medium_confidence'
333
+ };
334
+ } else if (image.detectedType === 'orm' && !result.ormMap) {
335
+ result.ormMap = {
336
+ file: image.file,
337
+ fileName: image.name,
338
+ source: 'name_detection_medium_confidence'
339
+ };
340
+ }
341
+ }
342
+ }
343
+
344
+ const unassignedImages = sortedImages.filter(image => {
345
+ if (result.baseColor && result.baseColor.fileName === image.name) return false;
346
+ if (result.normalMap && result.normalMap.fileName === image.name) return false;
347
+ if (result.ormMap && result.ormMap.fileName === image.name) return false;
348
+ return true;
349
+ });
350
+
351
+ if (unassignedImages.length > 0) {
352
+ const contentClassifications = await classifyAtlasTextures(unassignedImages);
353
+
354
+ if (!result.baseColor) {
355
+ const bestBaseColor = findBestMatch(contentClassifications, 'base_color');
356
+ if (bestBaseColor) {
357
+ result.baseColor = {
358
+ file: bestBaseColor.file,
359
+ fileName: bestBaseColor.name,
360
+ source: 'content_classification'
361
+ };
362
+ }
363
+ }
364
+
365
+ if (!result.normalMap) {
366
+ const bestNormalMap = findBestMatch(contentClassifications, 'normal_map');
367
+ if (bestNormalMap) {
368
+ result.normalMap = {
369
+ file: bestNormalMap.file,
370
+ fileName: bestNormalMap.name,
371
+ source: 'content_classification'
372
+ };
373
+ }
374
+ }
375
+
376
+ if (!result.ormMap) {
377
+ const bestOrmMap = findBestMatch(contentClassifications, 'orm_map');
378
+ if (bestOrmMap) {
379
+ result.ormMap = {
380
+ file: bestOrmMap.file,
381
+ fileName: bestOrmMap.name,
382
+ source: 'content_classification'
383
+ };
384
+ }
385
+ }
386
+ }
387
+
388
+ result.unclassified = sortedImages.filter(image => {
389
+ if (result.baseColor && result.baseColor.fileName === image.name) return false;
390
+ if (result.normalMap && result.normalMap.fileName === image.name) return false;
391
+ if (result.ormMap && result.ormMap.fileName === image.name) return false;
392
+ return true;
393
+ });
394
+
395
+ return result;
396
+ }
397
+
398
+ async function classifyAtlasTextures(images) {
399
+ const classifier = new TextureClassifier();
400
+ const classifications = [];
401
+
402
+ for (const imageInfo of images) {
403
+ try {
404
+ const img = document.createElement('img');
405
+
406
+ const dataUrl = await new Promise((resolve) => {
407
+ const reader = new FileReader();
408
+ reader.onload = () => resolve(reader.result);
409
+ reader.readAsDataURL(imageInfo.file);
410
+ });
411
+
412
+ await new Promise((resolve, reject) => {
413
+ img.onload = resolve;
414
+ img.onerror = reject;
415
+ img.src = dataUrl;
416
+ });
417
+
418
+ let classification = null;
419
+
420
+ if (typeof classifier.classifyImage === 'function') {
421
+ classification = classifier.classifyImage(img);
422
+ } else {
423
+ const filenameLower = imageInfo.name.toLowerCase();
424
+
425
+ classification = {
426
+ base_color: null,
427
+ normal_map: null,
428
+ orm_map: null
429
+ };
430
+
431
+ if (filenameLower.includes('basecolor') ||
432
+ filenameLower.includes('base_color') ||
433
+ filenameLower.includes('albedo') ||
434
+ filenameLower.includes('diffuse') ||
435
+ filenameLower.includes('color') && !filenameLower.includes('normal')) {
436
+ classification.base_color = { confidence: 0.8 };
437
+ }
438
+
439
+ if (filenameLower.includes('normal') ||
440
+ filenameLower.includes('nrm') ||
441
+ filenameLower.includes('nor')) {
442
+ classification.normal_map = { confidence: 0.8 };
443
+ }
444
+
445
+ if (filenameLower.includes('orm') ||
446
+ filenameLower.includes('occlusion') ||
447
+ filenameLower.includes('roughness') ||
448
+ filenameLower.includes('metallic') ||
449
+ filenameLower.includes('metalness')) {
450
+ classification.orm_map = { confidence: 0.8 };
451
+ }
452
+ }
453
+
454
+ classification.file = imageInfo.file;
455
+ classification.name = imageInfo.name;
456
+
457
+ classifications.push(classification);
458
+ } catch (error) {
459
+ // Classification failed for this image
460
+ }
461
+ }
462
+
463
+ return classifications;
464
+ }
465
+
466
+ function findBestMatch(classifications, textureType) {
467
+ if (!classifications || classifications.length === 0) {
468
+ return null;
469
+ }
470
+
471
+ const matches = classifications.filter(c => c[textureType]);
472
+
473
+ if (matches.length === 0) {
474
+ return null;
475
+ }
476
+
477
+ matches.sort((a, b) => {
478
+ const confidenceA = a[textureType] ? a[textureType].confidence : 0;
479
+ const confidenceB = b[textureType] ? b[textureType].confidence : 0;
480
+ return confidenceB - confidenceA;
481
+ });
482
+
483
+ return matches[0];
484
+ }
485
+
486
+ function getImageMimeType(extension) {
487
+ switch (extension.toLowerCase()) {
488
+ case '.jpg':
489
+ case '.jpeg':
490
+ return 'image/jpeg';
491
+ case '.png':
492
+ return 'image/png';
493
+ case '.webp':
494
+ return 'image/webp';
495
+ case '.tiff':
496
+ case '.tif':
497
+ return 'image/tiff';
498
+ default:
499
+ return 'application/octet-stream';
500
+ }
501
+ }
502
+
503
+ export function loadTextureIntoDropzone(file, type) {
504
+ if (!file) return;
505
+
506
+ if (!['basecolor', 'normal', 'orm'].includes(type)) {
507
+ return;
508
+ }
509
+
510
+ const fileList = {
511
+ 0: file,
512
+ length: 1,
513
+ item: (index) => index === 0 ? file : null
514
+ };
515
+
516
+ const dropEvent = new Event('drop', {
517
+ bubbles: true,
518
+ cancelable: true
519
+ });
520
+
521
+ Object.defineProperty(dropEvent, 'dataTransfer', {
522
+ value: {
523
+ files: fileList
524
+ }
525
+ });
526
+
527
+ const dropzone = document.getElementById(`${type}-dropzone`);
528
+
529
+ if (dropzone) {
530
+ dropzone.dispatchEvent(dropEvent);
531
+ }
532
+ }
533
+
534
+ export function loadModelIntoDropzone(file) {
535
+ if (!file) return;
536
+
537
+ const fileList = {
538
+ 0: file,
539
+ length: 1,
540
+ item: (index) => index === 0 ? file : null
541
+ };
542
+
543
+ const dropEvent = new Event('drop', {
544
+ bubbles: true,
545
+ cancelable: true
546
+ });
547
+
548
+ Object.defineProperty(dropEvent, 'dataTransfer', {
549
+ value: {
550
+ files: fileList
551
+ }
552
+ });
553
+
554
+ const dropzone = document.getElementById('model-dropzone');
555
+
556
+ if (dropzone) {
557
+ dropzone.dispatchEvent(dropEvent);
558
+ }
559
+ }
560
+
561
+ export function loadLightingIntoDropzone(file) {
562
+ if (!file) return;
563
+
564
+ const fileList = {
565
+ 0: file,
566
+ length: 1,
567
+ item: (index) => index === 0 ? file : null
568
+ };
569
+
570
+ const dropEvent = new Event('drop', {
571
+ bubbles: true,
572
+ cancelable: true
573
+ });
574
+
575
+ Object.defineProperty(dropEvent, 'dataTransfer', {
576
+ value: {
577
+ files: fileList
578
+ }
579
+ });
580
+
581
+ const dropzone = document.getElementById('lighting-dropzone');
582
+
583
+ if (dropzone) {
584
+ dropzone.dispatchEvent(dropEvent);
585
+ }
586
+ }
587
+
588
+ export function loadBackgroundIntoDropzone(file) {
589
+ if (!file) return;
590
+
591
+ const fileList = {
592
+ 0: file,
593
+ length: 1,
594
+ item: (index) => index === 0 ? file : null
595
+ };
596
+
597
+ const dropEvent = new Event('drop', {
598
+ bubbles: true,
599
+ cancelable: true
600
+ });
601
+
602
+ Object.defineProperty(dropEvent, 'dataTransfer', {
603
+ value: {
604
+ files: fileList
605
+ }
606
+ });
607
+
608
+ const dropzone = document.getElementById('background-dropzone');
609
+
610
+ if (dropzone) {
611
+ dropzone.dispatchEvent(dropEvent);
612
+ }
613
+ }
614
+
615
+ export function updateStateWithBestTextures(atlasResults) {
616
+ if (!atlasResults) return;
617
+
618
+ if (atlasResults.baseColor) {
619
+ updateState({
620
+ textureObjects: {
621
+ baseColor: atlasResults.baseColor.file
622
+ }
623
+ });
624
+ }
625
+
626
+ if (atlasResults.normalMap) {
627
+ updateState({
628
+ textureObjects: {
629
+ normal: atlasResults.normalMap.file
630
+ }
631
+ });
632
+ }
633
+
634
+ if (atlasResults.ormMap) {
635
+ updateState({
636
+ textureObjects: {
637
+ orm: atlasResults.ormMap.file
638
+ }
639
+ });
640
+ }
641
+ }
642
+
643
+ export function updateStateWithOtherAssets(results) {
644
+ if (!results || !results.success) return;
645
+
646
+ const updates = {};
647
+
648
+ if (results.modelFile) {
649
+ updates.modelFile = results.modelFile;
650
+ updates.useCustomModel = true;
651
+ }
652
+
653
+ if (results.lightingFile) {
654
+ updates.lightingFile = results.lightingFile;
655
+ }
656
+
657
+ if (results.backgroundFile && results.hasNamedBackground) {
658
+ updates.backgroundFile = results.backgroundFile;
659
+ }
660
+
661
+ if (Object.keys(updates).length > 0) {
662
+ updateState(updates);
663
+ }
664
+ }
665
+
666
+ export function handleZipUpload(file, infoElement, previewElement, dropzone) {
667
+ updateState('zipFile', file);
668
+
669
+ const zipInfoElement = document.getElementById('zip-info');
670
+ if (zipInfoElement) {
671
+ zipInfoElement.textContent = `ZIP file received: ${file.name} (${formatFileSize(file.size)})`;
672
+ zipInfoElement.style.display = 'block';
673
+ zipInfoElement.style.color = '';
674
+
675
+ setTimeout(() => {
676
+ zipInfoElement.style.display = 'none';
677
+ }, 5000);
678
+ }
679
+
680
+ processZipContents(file);
681
+
682
+ const event = new CustomEvent('zip-uploaded', {
683
+ detail: { file }
684
+ });
685
+ document.dispatchEvent(event);
686
+ }